Best Practices

Query

Avoid creating selectors in your components:

nav.components.ts
@Component({ ... })
class NavComponent {
isLoggedIn$ = this.authQuery.select(state => !!state.token);
constructor(private authQuery: AuthQuery) {
}
}

Instead, create them in the query:

auth.query.ts
export class AuthQuery extends Query<AuthState> {
isLoggedIn$ = this.select(state => !!state.token);
constructor(protected store: AuthStore) {
super(store);
}
}

This makes your components cleaner and the selectors reusable across the application.

When possible, try to avoid injecting the Query in the service. Instead, use the fact that it's already injected in the component and pass the required data into the service's method:

@Component({ ... })
class ProductsComponent {
constructor(private productsQuery: ProductsQuery,
private productsService: productsService) {}
saveProduct() {
const value = this.productsQuery.getValue().someValue;
this.productsService.doSomething(value);
}
}

This rule isn't written in stone; There are cases when you'll need to inject the Query in the service, for instance when using the caching feature, and that approach is totally valid.

Data Storage Types

Store only plain objects in the store, avoid storing Map or Set.

export function createInitialState() {
return {
prop: new Set(),
propTwo: new Map()
};
}
@StoreConfig({ name: 'store' })
export class SomeStore extends Store<any> {
constructor() {
super(createInitialState());
}
}

It's more efficient to perform immutable operations (which are required for updating the store) with plain objects rather than with complex ones such as Map or Set.

Transactions

Use transactions in Store methods to avoid multiple redundant dispatches being triggered in the Query selectors that use them.

UI State

Keep the UI data separated from the model data. Read more about this topic here.

Keep It Simple

Avoid over-engineering. Don't create a separate store for any entity you may have. For example, you might have a list of articles where each article contains a list of comments:

interface Article {
id: ID;
comments: Comment[];
title: string;
}
interface ArticlesState extends EntityState<Article> {
}
@StoreConfig({ name: 'articles' })
class ArticlesStore extends EntityStore<ArticlesState> {
constructor() {
super();
}
}

In most cases, there is no need to create a separate entity store for the comments. Instead use Akita's Array Utils. This will keep your store easier to maintain and use. If you still require a separate store, check out this article for tips on how to combine their data:

Error Handling

See the dedicated section about this.

Subscription Management

A question that often comes up is whether to subscribe at the component or the service. Let's examine the two options:

class TodosComponent {
ngOnInit() {
this.todoService.get().subscribe();
}
}
class TodoService {
get() {
return this.http.get<Todo[]>('/api/todos').pipe(
tap(entities => {
this.todoStore.set(entities);
})
);
}
}

Prefer this option when you need to show the user a success or error message locally in the component:

class TodosComponent {
ngOnInit() {
this.todoService.get().subscribe({
next() {
this.success = true;
}
error(err) {
this.error = err;
}
});
}
}

Otherwise, you can subscribe at the service:

class TodosComponent {
ngOnInit() {
this.todoService.get();
}
}
class TodoService {
get() {
return this.http.get<Todo[]>('/api/todos').subscribe(entities => {
this.todoStore.set(entities);
});
}
}