Managing state in Angular using Mobx

Original author: Netanel Basal
  • Transfer

State management


Every developer knows that state management is a pretty complicated thing. Keeping track of where and when has changed is a nightmare, especially in large applications.


There are several solutions in the Angular world that can make state management less complicated, painful, and fragile.


The two most popular solutions are ngrx/store, inspired for the most part by Redux, and Observable data services .


Personally, I really like Redux, and it costs every line of boilerplate code. But, unfortunately, some may disagree with me or Redux is not particularly applicable in their applications.


Therefore, I decided to tell you how Mobx can be useful in solving the problem of state management. The idea is to combine the two worlds, Redux and Mobx.


So, let's take the Redux immunity, the power of Rx + ngrx, and the Mobx state management capabilities. This combination will allow you to use asynchronous pipes in combination with a OnPushstrategy to achieve maximum performance.


Before we begin, it is understood that you have sufficient knowledge of Mobx and Angular.


For simplicity, we will create a traditional tudu application. Well, let's begin?


Side


I want to adhere to the principle of a single responsibility , so I create a filter for the filter (you can combine them into one if necessary).


Let's create a filter side.


import { Injectable } from '@angular/core';
import { action, observable} from 'mobx';
export type TodosFilter = 'SHOW_ALL' | 'SHOW_COMPLETED' | 'SHOW_ACTIVE';
@Injectable()
export class TodosFilterStore {
  @observable filter = 'SHOW_ALL';
  @action setFilter(filter: TodosFilter) {
    this.filter = filter;
  }
}

Add a side to the dudushki.


export class Todo {
  completed = false;
  title : string;
  constructor( { title, completed = false } ) {
    this.completed = completed;
    this.title = title;
  }
}
@Injectable()
export class TodosStore {
  @observable todos: Todo[] = [new Todo({ title: 'Learn Mobx' })];
  constructor( private _todosFilter: TodosFilterStore ) {}
  @action addTodo( { title } : Partial ) {
    this.todos = [...this.todos, new Todo({ title })]
  }
  @computed get filteredTodos() {
    switch( this._todosFilter.filter ) {
      case 'SHOW_ALL':
        return this.todos;
      case 'SHOW_COMPLETED':
        return this.todos.filter(t => t.completed);
      case 'SHOW_ACTIVE':
        return this.todos.filter(t => !t.completed);
    }
  }
}

If you are familiar with Mobx, the code above seems pretty straightforward.


Note that it’s good practice to always use a @actiondecorator. It helps to adhere to the concept of "Do not change the state directly," known to us since Redux. Mobx docks say:


In strict mode, it is not allowed to change the state outside the action.

RxJS Bridge


One of the cool things about RxJS is the ability to convert any data source into an RxJS Observable. In our case, we will use a computedfunction from Mobx to listen to the state change and give it to our subscribers in the Observable.


import { Observable } from 'rxjs/Observable';
import { computed } from 'mobx';
export function fromMobx( expression: () => T ) : Observable {
  return new Observable(observer => {
    const computedValue = computed(expression);
    const disposer = computedValue.observe(changes => {
      observer.next(changes.newValue);
    }, true);
    return () => {
      disposer && disposer();
    }
  });
}

In Rx, computedit’s sort of BehaviorSubjectmixed up withdistinctUntilChanged()


Every time there is a change (change by reference) in the expression, a callback is executed, which passes the new value to our subscribers. Now we have a bridge between Mobx and Rx.


Component


Let's create a component that will receive Input()and emit an event when selected.
Please note that here we use a onPushstrategy to identify changes.


@Component({
  selector: 'app-todo',
  template: `
    
    {{todo.title}}
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class TodoComponent {
  @Input() todo: Todo;
  @Output() complete = new EventEmitter();
}

Toad List Component


Let's create a todo list component that receives Input()and emits an event when something is selected.
Please note that here we use a onPushstrategy to identify changes.


@Component({
  selector: 'app-todos',
  template: `
    
`, changeDetection: ChangeDetectionStrategy.OnPush }) export class TodosComponent { @Input() todos: Todo[] = []; @Output() complete = new EventEmitter(); }

Plugin page component


@Component({
  selector: 'app-todos-page',
  template: `
   
  `
})
export class TodosPageComponent {
  todos : Observable;
  constructor( private _todosStore: TodosStore ) {
  }
  ngOnInit() {
    this.todos = fromMobx(() => this._todosStore.filteredTodos);
  }
  addTodo() {
    this._todosStore.addTodo({ title: `Todo ${makeid()}` });
  }
}

If you have worked with ngrx/store, you will feel at home. The property todosis Rx Observable and will only work when a filteredTodosproperty change occurs in our store.


A property filteredTodosis a computedvalue that triggers a change if a pure change occurs in filteror in the todosproperty of our store.


And of course, we get all Rx buns such as combineLatest(), take()etc., since now it is an Rx stream.


It's all. Here is a ready-made example .


I think you liked this little concept, I hope you were interested.


noticed a black hole in PM


Also popular now: