Lifecycle

The component class has special system methods which will be called at a certain moment.

Component.prototype.constructor(element, scope)

  • element HTMLElement - html element object controlled by the component
  • scope Proxy - object to synchronize with the component template (content)
class MyComponent extends Akili.Component {
  constructor(element, scope) {
    super(element, scope);

    console.log(element === this.el); // true;
    console.log(scope === this.scope); // true;
  }
}

First of all, during the compilation it will be created instance of the component. Constructor gets element and scope. Here you can do anything with the element and its content. It is the place where you can prepare your component to compilation: change element and set default scope values, for example.

class MyComponent extends Akili.Component {
  constructor(element, scope) {
    super(element, scope);

    element.innerHTML = '${ this.example }';
    element.setAttribute('class', '${ this.className }')

    scope.example = 'Hello!';
    scope.className = 'example';
  }
}
Cancel the compilation

You can stop the component compilation if it is necessary. It is possible only in the constructor.

class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    if(!this.el.hasAttribute('x')) {
      this.cancel();
    }
  }
}

So if the element does not have attribute x, the compilation will not happen. Further steps will be stopped. Another way to stop compiling without even getting into the constructor is matches property using.

class MyComponent extends Akili.Component {
  static matches = '[x]';
}

Both examples above are equivalent

Prevent the compilation

Also you can prevent the compilation inside of the component.

class Code extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.prevent();
  }
}
<code>${ example }</code>

After the compilation there will be the same code inside of the component, because the compilation was prevented.

<code>${ example }</code>

Component.prototype.created()

This is a logical extension of the constructor and will be called immediately after that, if you didn't cancel the compilation in the constructor. At this level, expressions in the component template is not parsed yet.

Component.prototype.compiled() [Promise]

At this level, all expressions will be parsed. Also you can get the component attributes here. So this is a great place to start actions associated with the attributes of the component.

You can return promise here. In this case, resolved method will be called after all such promises resolving.

Component.prototype.recompiled()

It will be called during each recompilation of the component.

Component.prototype.resolved() [Promise]

This method will be called after the full compilation of the component and all nested components, even if they use asynchronous loading of their templates. So you have access to all the children components here.

class ParentComponent extends Akili.Component {
  resolved() {
    this.child('child-component').setTitle();
  }
}

class ChildComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.title = '';
  }

  setTitle() {
    this.scope.title = 'Look at this!';
  }
}
<parent-component>
  <child-component>
    ${ this.title }
  </child-component>
</parent-component>

During the compilation all the stages of the component occur from top to bottom and from left to right in the hierarchy. But at this stage everything is the other way: from right to left and from bottom to top.

Component.prototype.removed()

This method will be called on the component removing. You can do something here before it.

Component.prototype.changed(key, value)

  • key string - attribute name
  • value * - attribute value

This method will be called every time when any attribute of the component is changed through the framework binding system.

class ParentComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.childTitle = 'Old title';
  }

  compiled() {
    setTimeout(() => {
      this.scope.childTitle = 'New title';
    }, 1000);
  }
}

class ChildComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.title = '';
  }

  changed(key, value) {
    this.scope.title = value;
  }
}
<parent-component>
  <child-component title="${ this.childTitle }">
    ${ this.title }
  </child-component>
</parent-component>

First inside the child component we will see Old title, in a second New title. For convenience we added the ability to watch separately for each attribute.

class MyComponent extends Akili.Component {
  changed(key, value) {
    if(key == 'title') {
      // do something
    }
  }
}
class MyComponent extends Akili.Component {
  changedTitle(value) {
    // do something
  }
}

The two examples above are equivalents.

The attribute binding system allows you to encapsulate the component. Now when you write some independent component, you do not care what happens in others. You will learn about the necessity to change something when any attribute changes.