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', this);

    router.add('app', '/app', {
      title: 'My application',
      component: this
    }
  }

  compiled() {
    // 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.

How to write your own library

Let's take that your library consist of two components and the service.

import Component from 'akili/src/component';

// First component
export class MyComponent extends Component {
  static define() {
    Akili.component('my-component', this);
    Akili.component('my-component-part', this.MyComponentPart);
  }

  created() {
    this.el.addEventListener('click', this.setColor.bind(this));
  }

  setColor() {
    const child = this.child(c => c instanceof MyComponentPart);
    const color = MyService.getColor();
    child.setMyColor(color);
  }
}

// Second component
export class MyComponentPart extends Component {
  setMyColor(color) {
    this.el.style.color = color;
  }
}

// Service
export const MyService = {
  color: 'blue',
  setColor(color) {
    this.color = color;
  },
  getColor() {
    return this.color;
  }
}

MyComponent.MyComponentPart = MyComponentPart;
Akili.components.MyComponent = MyComponent;
Akili.services.MyService = MyService;

The library using with the building system:

import Akili from 'akili';
import { MyComponent, MyService } from 'my-component';

MyComponent.define();
MyService.setColor('red');

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});
<my-component>
  <my-component-part>
    ${ this.title }
  </my-component-part>
</my-component>

The library using without the building system:

Akili.components.MyComponent.define();
Akili.services.MyService.setColor('red');

document.addEventListener('DOMContentLoaded', () => {
  Akili.init().catch((err) => console.error(err));
});
<script src="akili.js">
<script src="my-component.js">
<my-component>
  <my-component-part>
    ${ this.title }
  </my-component-part>
</my-component>

More about the module system of the framework is here.

Encapsulation through attributes

It is better always pass some data to the component through the attributes. In this case the component will be independent and might be used anywhere.

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 {
  compiled() {
    this.attr('title', 'myTitle'); // link with the scope property
    this.attr('title', val => console.log(val)); // or handle it with the callback
  }
}
<first-component>
  <second-component title="${ this.title }">
    ${ this.myTitle }
  </second-component>
</first-component>

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

Data sharing

If you want the components with different purposes to communicate with each other use the store. Don't use the relations if you work with the business data.

Bad
class FirstComponent extends Akili.Component {
  resolved() {
    Akili.root.child('second-component').scope.data = ['something'];
  }
}

class SecondComponent extends Akili.Component {
  created() {
    this.scope.data = [];
  }
}
Good
class FirstComponent extends Akili.Component {
  compiled() {
    this.store('data', 'data');
  }

  resolved() {
    Akili.services.store.data = ['something'];
  }
}

class SecondComponent extends Akili.Component {
  compiled() {
    this.store('data', 'data');
  }
}
<first-component>${ this.data }</first-component>
<second-component>${ this.data }</second-component>

Template expressions

Turning to nested properties of the object check at first the existence.

Not always good
<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 the system events. Sometimes we would get undesirable events. So it is better to use the custom names if you don't need exactly the system one to do something.

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.