To start a local development server, run:
ng serveOnce the server is running, open your browser and navigate to http://localhost:4200/. The application will automatically reload whenever you modify any of the source files.
Ziel: Zwei Angular Standalone Components mit NgRx bauen.
TodoListComponent: nutzt nur NgRx Store. Keine HTTP-API, kein CRUD-Service, keine Effects.RandomUserComponent: nutzt NgRx Store + Effects +HttpClientgegenhttps://randomuser.me/api/.
npm install @ngrx/store @ngrx/effects @ngrx/store-devtools@ngrx/store haelt State. @ngrx/effects kapselt Side Effects wie HTTP. @ngrx/store-devtools zeigt Actions und State im Redux DevTools Browser-Plugin.
Datei: src/app/app.config.ts
provideHttpClient(),
provideStore({
[todosFeatureKey]: todosReducer,
[randomUsersFeatureKey]: randomUsersReducer,
}),
provideEffects([RandomUserEffects]),
provideStoreDevtools({
maxAge: 25,
logOnly: !isDevMode(),
}),Damit kennt Angular beide Feature States. provideHttpClient() wird nur fuer RandomUser gebraucht.
Dateien:
src/app/state/todos/todo.model.tssrc/app/state/todos/todo.actions.tssrc/app/state/todos/todo.reducer.tssrc/app/state/todos/todo.selectors.tssrc/app/todo-list/todo-list.component.ts
Flow:
Component -> dispatch(Action) -> Reducer -> neuer State -> Selector -> Template
Actions:
export const todoActions = createActionGroup({
source: 'Todo List',
events: {
'Add Todo': props<{ title: string }>(),
'Toggle Todo': props<{ id: string }>(),
'Remove Todo': props<{ id: string }>(),
'Clear Completed': emptyProps(),
},
});Reducer:
on(todoActions.toggleTodo, (state, { id }) => ({
...state,
items: state.items.map((todo) =>
todo.id === id ? { ...todo, completed: !todo.completed } : todo,
),
}))Wichtig: Reducer mutiert state.items nicht. Er gibt neues Array und neue Todo-Objekte zurueck.
Component:
protected readonly todos$ = this.store.select(selectTodos);
protected toggleTodo(todo: Todo): void {
this.store.dispatch(todoActions.toggleTodo({ id: todo.id }));
}Template:
@for (todo of todos$ | async; track todo.id) {
<li [class.done]="todo.completed">
<input type="checkbox" [checked]="todo.completed" (change)="toggleTodo(todo)" />
<span>{{ todo.title }}</span>
</li>
}Kein Effect noetig, weil kein externer Seiteneffekt passiert. Alles bleibt synchron im Store.
Dateien:
src/app/state/random-users/random-user.model.tssrc/app/state/random-users/random-user.actions.tssrc/app/state/random-users/random-user.reducer.tssrc/app/state/random-users/random-user.selectors.tssrc/app/state/random-users/random-user.service.tssrc/app/state/random-users/random-user.effects.tssrc/app/random-user/random-user.component.ts
Flow:
Component -> Load Users Action -> Effect -> RandomUserService -> API
API -> Success/Failure Action -> Reducer -> Selector -> Template
Component dispatch:
this.store.dispatch(randomUserActions.loadUsers({ query }));Effect:
readonly loadUsers$ = createEffect(() =>
this.actions$.pipe(
ofType(randomUserActions.loadUsers),
switchMap(({ query }) =>
this.randomUserService.loadUsers(query).pipe(
map((users) => randomUserActions.loadUsersSuccess({ users, query })),
catchError(() =>
of(randomUserActions.loadUsersFailure({ error: 'RandomUser API Fehler' })),
),
),
),
),
);Service:
return this.http.get<RandomUserApiResponse>('https://randomuser.me/api/', { params });Reducer speichert loading, error, query und users. Template zeigt User per users$ | async.
App starten:
npm startDann Browser auf http://localhost:4200/ oeffnen und Redux DevTools nutzen.
Bei Todo siehst du:
[Todo List] Add Todo[Todo List] Toggle Todo[Todo List] Remove Todo
Bei RandomUser siehst du:
[Random User] Load Users[Random User] Load Users Success[Random User] Load Users Failure
Merksatz: Store beschreibt Zustand. Actions beschreiben Ereignisse. Reducer berechnet neuen Zustand. Effects sprechen mit Welt ausserhalb Angular.