Voici une petite explication de l'utilisation du Pipe async :
Le Pipe async souscrit à un Observable ou une Promise qui renvoie la dernière valeur qu'il a émise. Lorsqu'une nouvelle valeur est détectée, le canal asynchrone envoie un signale au component afin qu'il mette à jour la donnée.
Lorsque le component est détruit, le Pipe async se désinscrit automatiquement afin d'éviter les fuites de mémoire potentielles.
La syntaxe de la liste de todos :
<li *ngFor="let todo of (todos$ | async)?.data">
Cependant, en changeant l'argument du select('todos') par une fonction, on peut obtenir une syntaxe plus simplifiée :
<li *ngFor="let todo of todos$ | async">
this.todos$ = store.pipe(select((state) => state.todos.data)); // On cible directement la propriété data
Avant de continuer sur nos fonctions select, un point sur le Pipe RXJS s'impose.
Le Pipe permet de réaliser un chaînage d'opérateurs RXJS de manière plus lisible.
Exemple de Pipe :
const { Observable } = require('rxjs/Rx')
const { filter, map, reduce } = require('rxjs/operators')
const { Pipe } = require('rxjs/Rx')
const filterOutEvens = filter(x => x % 2)
const doubleBy = x => map(value => value \* x);
const sum = reduce((acc, next) => acc \+ next, 0);
const source$ = Observable.range(0, 10)
source$
.pipe(
filterOutEvens,
doubleBy(2),
sum
).subscribe(console.log); // 50
Aperçu des différents opérateurs : http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html
La fonction select de NGRX peut prendre une fonction en paramètre. On peut donc déporter cette logique et la stocker dans un fichier dédié. Grâce à la fonction createSelector, on pourra composer des sélecteurs à partir d'autres sélecteurs.
store/selectors/todo-list.selector.ts
import { createSelector } from '@ngrx/store';
// La première fonction amène vers le state todos
export const selectTodoListState$ = (state: AppState) => state.todos;
// Et à partir de celle-ci, on créer une autre fonction qui renverra data
export const selectTodos$ = createSelector(selectTodoListState$,(todos) => todos.data);
/app.component.ts
import { selectTodos } from 'store/selectors/todo-list.selector';
// [...]
// On remplace la fonction par le sélecteur
this.todos$ = store.pipe(select(selectTodos$));
Utiliser un formulaire pour créer une todo grâce au FormsBuilder d'Angular. La fonction createTodo renverra la future action de création dans le reducer.
/app.component.ts
import { Store, select } from '@ngrx/store';
import { OnInit, Component, Inject } from '@angular/core';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
import { Observable } from 'rxjs/Observable';
import { TodoListModule } from './store/actions/todo-list.action';
import { AppState } from './store';
import { Todo } from './models/todo';
import { selectTodos$ } from './store/selectors/todo-list.selector';
@Component({
selector: 'app-root',
styleUrls: ['./app.component.scss'],
template: `
<h1>la todolist redux style !</h1>
<form [formGroup]="todoForm" (ngSubmit)="createTodo(todoForm.value)">
<label>Titre :</label>
<input type="text" formControlName="title" placeholder="Title"/>
<label>Est-elle terminé ? :</label>
<input type="checkbox" formControlName="completed"/>
<button>Créer</button>
</form>
<ul>
<li *ngFor="let todo of todos$ | async">
<label>{{ todo.title }}</label>
<input type="checkbox" [ngModel]="todo.completed"/>
<button>Supprimer</button>
</li>
</ul>
`
})
export class AppComponent implements OnInit {
todos$: Observable<Todo[]>;
public todoForm: FormGroup;
constructor(
private store: Store<AppState>,
@Inject(FormBuilder) fb: FormBuilder
) {
this.todos$ = store.Pipe(select(selectTodos$));
this.todoForm = fb.group({
title: ['', Validators.required],
completed: [false, Validators]
});
}
createTodo(todo: Todo) {
const payload = {
...todo,
userId: 1,
id: 8
};
this.store.dispatch(new TodoListModule.CreateTodo(payload));
this.todoForm.reset();
}
ngOnInit() {
this.store.dispatch(new TodoListModule.InitTodos());
}
}
Ne pas oublier de charger les modules pour utiliser les formulaires d'Angular.
/app.module.ts
// [...]
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
// [...]
imports: [
ReactiveFormsModule,
FormsModule,
// [...]
],
// [...]
Créer l'action pour le reducer :
store/actions/todo-list.action.ts
import { Todo } from '../../models/todo';
export namespace TodoListModule {
export enum ActionTypes {
// [...]
CREATE_TODO = '[todoList] Create Todo',
}
// [...]
export class CreateTodo {
readonly type = ActionTypes.CREATE_TODO;
constructor(public payload: Todo) {}
}
export type Actions = InitTodos | CreateTodo;
}
Cette action transmet un payload qui sera la nouvelle todo à ajouter à notre tableau :
/store/reducers/todo-list.reducer.ts
// [...]
case TodoListModule.ActionTypes.CREATE_TODO:
return {
...state,
data: [
...state.data,
action.payload
]
};
// [...]
Notre action createTodo est terminée. Reste à revoir la gestion des ids.