Best practice

Component as a module

First of all, you can use our way for your components encapsulation.

import Akili from 'akili';
import Component from 'akili/src/component';
import router from 'akili/src/services/router';

class App extends Component {
  static define() {
    Akili.component('app', App);

    router.add('app', '/app', {
      template: '/templates/app.html'
    }
  }

  created() {
    // do something
  }
}
import App from './app';

App.define();

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});

This allows the component author to specify the default actions for his component: how to name it, what routing to add, etc. At the same time, the user who imports this component will be able to decide for himself whether to use the author settings or apply his own.

Scope initialization

It will be better if you always initialize the scope variables in the constructor or created methods before you use them.

Not good
class MyComponent extends Akili.Component {
  compiled() {
    this.scope.title = this.attrs.title;
  }
}
Good
class MyComponent extends Akili.Component {
  constructor(...args) {
    super(...args);

    this.scope.title = '';
  }

  compiled() {
    this.scope.title = this.attrs.title;
  }
}
class MyComponent extends Akili.Component {
  created() {
    this.scope.title = '';
  }

  compiled() {
    this.scope.title = this.attrs.title;
  }
}

Encapsulation through attributes

It is best to pass data to a component through its attributes. This allows it to be independent of external conditions.

Not always good
class FirstComponent extends Akili.Component {
  created() {
    this.scope.title = 'Hello';
  }
}

class SecondComponent extends Akili.Component {}
<first-component>
  <second-component>
    ${ this.title + 'World' }
  </second-component>
</first-component>
Good
class FirstComponent extends Akili.Component {
  created() {
    this.scope.title = 'Hello';
  }
}

class SecondComponent extends Akili.Component {
  created() {
    this.scope.myTitle = '';
  }

  changedTitle(title) {
    this.scope.myTitle = title;
  }

  compiled() {
    this.scope.myTitle = this.attrs.title;
  }
}
<first-component>
  <second-component title="${ this.title }">
    ${ this.myTitle }
  </second-component>
</first-component>

Of course, there may be situations when the parent and child components are interrelated. In this case, the first example is also suitable.

Expressions in templates

Refer to all nested properties of the object, checking first the existence of the object itself.

Bad
<my-component>
  ${ this.data.user.id }
</my-component>
Good
<my-component>
  ${ this.data && this.data.user && this.data.user.id }
</my-component>

If there is no object, an error will be thrown in the first example.

Custom event names

Not Good
class MyComponent extends Akili.Component {
  static events = ['change', 'click'];
}
Good
class MyComponent extends Akili.Component {
  static events = ['my-change', 'my-click'];
}

Events change, click and some other are also system events. Besides they are bubbling. It means that sometimes we will get extra events, if some element inside it generates the same event.

class MyComponent extends Akili.Component {
  static events = ['change'];

  created() {
    this.scope.x = 0;
    this.scope.y = 0;
  }

  compiled() {
    this.attrs.onChange.trigger();
  }
}
<my-component on-change="${ this.y++ }">
  <input type="checkbox" on-change="${ this.x++ }"/>
</my-component>

If you click on the checkbox you will have x == 1, y == 2.