Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Service
Component
Component
Component
Service
View
Action
Store
current state
iteraction
dispatch action
reducer
state / action
new state
> ng new angular-ngrx-example
> npm install @ngrx/{store, effects, entity, store-devtools} --save
import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
// ...
@NgModule({
imports: [
StoreModule.forRoot(reducers),
EffectsModule.forRoot([]),
StoreRouterConnectingModule,
!environment.production ? StoreDevtoolsModule.instrument({ maxAge: 50 }) : []
]
})
// ...
@NgModule({
imports: [
StoreModule.forFeature('task', taskReducer),
EffectsModule.forFeature([TaskEffects])
]
})
export class TasksModule {}
A plain JavaScript object
{
tasks: [
{
id: '1',
title: 'Task 01',
completed: false
},
{
id: '2',
title: 'Task 02',
completed: true
}
],
isLoading: false,
error: null
}
// step 1: define state and initial state
export interface TaskState {
tasks: Task[];
isLoading: boolean;
error: any;
}
export const taskInitialState: TaskState = {
tasks: [],
isLoading: true,
error: null
};
// selectors - select an information from the state
export const taskState = createFeatureSelector<TaskState>('task');
export const selectedRecords = createSelector(taskState, (state: TaskState) => state.tasks);
export const selectIsLoading = createSelector(taskState, (state: TaskState) => state.isLoading);
Request for changing the sate
What's the component responsability?
export enum TaskActionTypes {
LOAD = '[Task] LOAD Requested',
CREATE = '[Task] CREATE Requested',
UPDATE = '[Task] UPDATE Requested',
REMOVE = '[Task] REMOVE Requested',
ERROR = '[Task] Error'
}
What information needs to be included in each Action?
export class CreateAction implements Action {
type = TaskActionTypes.CREATE;
constructor(public payload: { task: Task }) { }
}
A pure function that accepts a state and an action and returns a new state
export const taskReducer: ActionReducer<Task[]> =
(state: Task[] = [], action: TaskAction) => {
switch (action.type) {
case TaskActionTypes.CREATE:
return [...state, action.payload.task];
case TaskActionTypes.REMOVE:
return state.filter((task: Task) => {
return task.id !== action.payload.task.id;
});
default:
return state;
}
};
export class TasksComponent implements OnInit {
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.store.dispatch(new Action.LoadAction());
}
);
export class TasksComponent implements OnInit {
tasks$: Observable<Task[]>;
constructor(private store: Store<AppState>) {}
ngOnInit() {
this.store.dispatch(new Action.LoadAction());
this.tasks$ = this.store.select(State.selectedRecords)
}
);
export const taskState = createFeatureSelector<TaskState>('task');
export const selectedRecords = createSelector(taskState, (state: TaskState) => state.tasks);
<mat-card>
<app-task-form (createTask)="onCreateTask($event)"></app-task-form>
<mat-spinner *ngIf="isLoading$ | async; else taskList"></mat-spinner>
<ng-template #taskList>
<app-tasks-list
[tasks]="tasks$"
(remove)="onRemoveTask($event)"
(edit)="onUpdateTask($event)">
</app-tasks-list>
</ng-template>
<div class="error-msg" *ngIf="error$ | async as error">
<p>{{ error }}</p>
</div>
</mat-card>
@Component({
selector: 'app-task-item',
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class TaskItemComponent {
@Input() task: Task;
@Output() remove: EventEmitter<any> = new EventEmitter(false);
@Output() edit: EventEmitter<any> = new EventEmitter(false);
onRemove() {
this.remove.emit();
}
onEdit() {
this.edit.emit(this.task);
}
}
Request for changing the sate
What's the component responsability?
export enum TaskActionTypes {
LOAD = '[Task] LOAD Requested',
LOAD_SUCCESS = '[Task] LOAD Success',
CREATE = '[Task] CREATE Requested',
CREATE_SUCCESS = '[Task] CREATE Success',
UPDATE = '[Task] UPDATE Requested',
UPDATE_SUCCESS = '[Task] UPDATE Success',
REMOVE = '[Task] REMOVE Requested',
REMOVE_SUCCESS = '[Task] REMOVE Success',
ERROR = '[Task] Error'
}
Make the request and wait for the response to complete the action
@Effect()
createAction$ = this.actions$.pipe(
ofType<Action.CreateAction>(Action.TaskActionTypes.CREATE),
map(action => action.payload),
mergeMap(payload =>
this.api.create(payload.task).pipe(
map(res => new Action.CreateActionSuccess({ task: res })),
catchError(error => this.handleError(error)))
));
export interface EntityState<T> {
ids: string[];
entities: Dictionary<T>;
}
export interface EntityStateAdapter<T> {
addOne<S extends EntityState<T>>(entity: T, state: S): S;
addMany<S extends EntityState<T>>(entities: T[], state: S): S;
addAll<S extends EntityState<T>>(entities: T[], state: S): S;
removeOne<S extends EntityState<T>>(key: string, state: S): S;
removeMany<S extends EntityState<T>>(keys: string[], state: S): S;
removeAll<S extends EntityState<T>>(state: S): S;
updateOne<S extends EntityState<T>>(update: Update<T>, state: S): S;
updateMany<S extends EntityState<T>>(updates: Update<T>[], state: S): S;
}
export declare type EntitySelectors<T, V> = {
selectIds: (state: V) => string[];
selectEntities: (state: V) => Dictionary<T>;
selectAll: (state: V) => T[];
selectTotal: (state: V) => number;
};
case TaskActions.LOAD_SUCCESS: {
return adapter.addMany(action.payload.tasks, state);
}
case TaskActions.CREATE_SUCCESS: {
return adapter.addOne(action.payload.task, state);
}
case TaskActions.UPDATE_SUCCESS: {
return adapter.updateOne(action.payload.task, state);
}
case TaskActions.UPSERT_SUCCESS: {
return adapter.upsertMany(action.payload.tasks, state);;
}
> npm install @ngrx/schematics --save-dev
> ng generate store State --root --module app.module.ts --collection @ngrx/schematics
> ng generate entity tasks/store/Task --module tasks/tasks.module.ts --collection @ngrx/schematics
> ng generate effect tasks/store/Task --module tasks/tasks.module.ts --collection @ngrx/schematics
Main setup
Feature module +
store feature module