|
1 |
| -# NgRx Component Store Crud |
| 1 | +# Angular CRUD implementation with different state management libraries |
2 | 2 | [](https://app.netlify.com/sites/component-store-crud/deploys)
|
3 | 3 |
|
4 | 4 | ### Features
|
5 |
| -- Component Store for CRUD state |
| 5 | +- rxState / NgRx Component-Store for CRUD state |
6 | 6 | - Smart / Dumb component architecture
|
7 | 7 | - Angular Material components
|
8 | 8 | - Table animations (on add, update, remove)
|
9 | 9 | - Error, Loading state handling
|
10 | 10 |
|
11 | 11 |
|
12 |
| -### Code parts |
13 |
| - |
14 |
| -#### TodosStore |
15 |
| -```ts |
16 |
| - |
17 |
| -@Injectable({ providedIn: 'root' }) |
18 |
| -export class TodosStore extends ComponentStore<TodosState> { |
19 |
| - |
20 |
| - constructor(private todosService: TodosService) { |
21 |
| - super(initialState); |
22 |
| - } |
23 |
| - |
24 |
| - loadTodos = this.effect((payload$: Observable<Partial<GetTodosPayload>>) => payload$.pipe( |
25 |
| - tap(() => this.patchState({ loading: true, loaded: false, error: null })), |
26 |
| - switchMap(payload => { |
27 |
| - const currentPayload = this.get(s => s.params); |
28 |
| - const newPayload = { ...currentPayload, ...payload }; |
29 |
| - return this.todosService.get(newPayload).pipe( |
30 |
| - tap((data: Todo[]) => |
31 |
| - this.patchState({ |
32 |
| - data, error: null, loading: false, loaded: true, params: newPayload, |
33 |
| - total: 100 // this should be retrieved from headers, or most of the time will come with the response body |
34 |
| - }) |
35 |
| - ), |
36 |
| - catchError(error => { |
37 |
| - this.patchState({ |
38 |
| - error, data: [], loading: false, loaded: false, params: initialState.params |
39 |
| - }); |
40 |
| - return EMPTY; // we return EMPTY in order to keep the effect observable alive |
41 |
| - }) |
42 |
| - ); |
43 |
| - }) |
44 |
| - )); |
45 |
| - |
46 |
| - addTodo = this.effect((title$: Observable<string>) => title$.pipe( |
47 |
| - concatMap(todoTitle => this.todosService.add(todoTitle).pipe( |
48 |
| - tap(todo => { |
49 |
| - const todos = this.get(s => s.data); |
50 |
| - todos.unshift(todo); |
51 |
| - this.patchState({ data: [ ...todos ] }) |
52 |
| - }), |
53 |
| - catchError(error => { |
54 |
| - console.error('Cannot add todo with title: ' + todoTitle, error); |
55 |
| - return EMPTY; |
56 |
| - }) |
57 |
| - )) |
58 |
| - )); |
59 |
| - |
60 |
| - updateTodo = this.effect((todo$: Observable<Todo>) => todo$.pipe( |
61 |
| - concatMap(todo => this.todosService.toggle(todo).pipe( |
62 |
| - tap(todo => { |
63 |
| - const todos = this.get(s => s.data); |
64 |
| - this.patchState({ |
65 |
| - // data: todos.map(x => x.id === todo.id ? { ...x, ...todo } : x) |
66 |
| - // in order to not loose the reference of the item and reanimate the enter transition |
67 |
| - // we dont change the reference of the item but only the needed key |
68 |
| - data: todos.map(item => { |
69 |
| - if (item.id === todo.id) { |
70 |
| - item.completed = todo.completed; |
71 |
| - } |
72 |
| - return item; |
73 |
| - }) |
74 |
| - }) |
75 |
| - }), |
76 |
| - catchError(error => { |
77 |
| - console.error('Cannot update todo with ID: ' + todo.id, error); |
78 |
| - return EMPTY; |
79 |
| - }) |
80 |
| - )) |
81 |
| - )); |
82 |
| - |
83 |
| - removeTodo = this.effect((todoId$: Observable<number>) => todoId$.pipe( |
84 |
| - concatMap(todoId => this.todosService.remove(todoId).pipe( |
85 |
| - tap(todoId => { |
86 |
| - const todos = this.get(s => s.data); |
87 |
| - this.patchState({ |
88 |
| - data: todos.filter(x => x.id !== todoId) |
89 |
| - }) |
90 |
| - }), |
91 |
| - catchError(error => { |
92 |
| - console.error('Cannot delete todo with ID: ' + todoId, error); |
93 |
| - return EMPTY; |
94 |
| - }) |
95 |
| - )) |
96 |
| - )); |
97 |
| - |
98 |
| -} |
99 |
| - |
100 |
| -``` |
101 |
| - |
102 |
| - |
103 |
| - |
104 |
| -#### Todos component |
105 |
| -```html |
106 |
| -<ng-container *ngIf="store.state$ | async as vm"> |
107 |
| - |
108 |
| - <todos-filter |
109 |
| - (filtered)="store.loadTodos({ searchQuery: $event })"> |
110 |
| - </todos-filter> |
111 |
| - |
112 |
| - <todos-table |
113 |
| - [todos]="vm.data" |
114 |
| - [totalRows]="vm.total" |
115 |
| - [loading]="vm.loading" |
116 |
| - (pageChanged)="store.loadTodos($event)" |
117 |
| - (sorted)="store.loadTodos({ sort: $event })" |
118 |
| - (todoToggled)="store.updateTodo($event)" |
119 |
| - (todoRemoved)="store.removeTodo($event)"> |
120 |
| - </todos-table> |
121 |
| - |
122 |
| - <div *ngIf="vm.error"> |
123 |
| - Error: {{ vm.error }} |
124 |
| - </div> |
125 |
| - |
126 |
| -</ng-container> |
127 |
| -``` |
128 |
| - |
129 |
| -```ts |
130 |
| -@Component({ |
131 |
| - selector: 'todos', |
132 |
| - template: `...`, |
133 |
| - changeDetection: ChangeDetectionStrategy.OnPush |
134 |
| -}) |
135 |
| -export class TodosComponent implements OnInit { |
136 |
| - |
137 |
| - constructor(public store: TodosStore) {} |
138 |
| - |
139 |
| - ngOnInit() { |
140 |
| - this.store.loadTodos({ pageSize: 10, pageIndex: 1 }); |
141 |
| - } |
142 |
| - |
143 |
| -} |
144 |
| -``` |
| 12 | +### Included state management libraries |
| 13 | +- RxState |
| 14 | +- NgRx Component-Store |
145 | 15 |
|
146 | 16 |
|
147 | 17 | 
|
|
0 commit comments