Differenzansicht 16e-ngrx
im Vergleich zu 16-guards

← Zurück zur Ãœbersicht | Demo | Quelltext auf GitHub
angular.json CHANGED
@@ -114,7 +114,8 @@
114
  },
115
  "cli": {
116
  "schematicCollections": [
117
- "angular-eslint"
 
118
  ]
119
  }
120
- }
114
  },
115
  "cli": {
116
  "schematicCollections": [
117
+ "angular-eslint",
118
+ "@ngrx/schematics"
119
  ]
120
  }
121
+ }
package.json CHANGED
@@ -20,6 +20,9 @@
20
  "@angular/platform-browser": "^19.1.0",
21
  "@angular/platform-browser-dynamic": "^19.1.0",
22
  "@angular/router": "^19.1.0",
 
 
 
23
  "angular-date-value-accessor": "^3.0.0",
24
  "book-monkey5-styles": "^1.0.4",
25
  "rxjs": "~7.8.0",
@@ -30,10 +33,12 @@
30
  "@angular-devkit/build-angular": "^19.1.1",
31
  "@angular/cli": "^19.1.1",
32
  "@angular/compiler-cli": "^19.1.0",
 
33
  "@types/jasmine": "~5.1.0",
34
  "angular-eslint": "19.0.2",
35
  "eslint": "^9.16.0",
36
  "jasmine-core": "~5.5.0",
 
37
  "karma": "~6.4.0",
38
  "karma-chrome-launcher": "~3.2.0",
39
  "karma-coverage": "~2.2.0",
20
  "@angular/platform-browser": "^19.1.0",
21
  "@angular/platform-browser-dynamic": "^19.1.0",
22
  "@angular/router": "^19.1.0",
23
+ "@ngrx/effects": "^19.0.0",
24
+ "@ngrx/store": "^19.0.0",
25
+ "@ngrx/store-devtools": "^19.0.0",
26
  "angular-date-value-accessor": "^3.0.0",
27
  "book-monkey5-styles": "^1.0.4",
28
  "rxjs": "~7.8.0",
33
  "@angular-devkit/build-angular": "^19.1.1",
34
  "@angular/cli": "^19.1.1",
35
  "@angular/compiler-cli": "^19.1.0",
36
+ "@ngrx/schematics": "^19.0.0",
37
  "@types/jasmine": "~5.1.0",
38
  "angular-eslint": "19.0.2",
39
  "eslint": "^9.16.0",
40
  "jasmine-core": "~5.5.0",
41
+ "jasmine-marbles": "^0.9.2",
42
  "karma": "~6.4.0",
43
  "karma-chrome-launcher": "~3.2.0",
44
  "karma-coverage": "~2.2.0",
src/app/app.module.ts CHANGED
@@ -1,6 +1,9 @@
1
- import { NgModule } from '@angular/core';
2
  import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
3
  import { BrowserModule } from '@angular/platform-browser';
 
 
 
4
 
5
  import { AppRoutingModule } from './app-routing.module';
6
  import { AppComponent } from './app.component';
@@ -17,6 +20,9 @@ import { AuthInterceptor } from './shared/auth.interceptor';
17
  imports: [
18
  BrowserModule,
19
  AppRoutingModule,
 
 
 
20
  ],
21
  providers: [
22
  provideHttpClient(withInterceptorsFromDi()),
1
+ import { NgModule, isDevMode } from '@angular/core';
2
  import { provideHttpClient, withInterceptorsFromDi, HTTP_INTERCEPTORS } from '@angular/common/http';
3
  import { BrowserModule } from '@angular/platform-browser';
4
+ import { StoreModule } from '@ngrx/store';
5
+ import { StoreDevtoolsModule } from '@ngrx/store-devtools';
6
+ import { EffectsModule } from '@ngrx/effects';
7
 
8
  import { AppRoutingModule } from './app-routing.module';
9
  import { AppComponent } from './app.component';
20
  imports: [
21
  BrowserModule,
22
  AppRoutingModule,
23
+ StoreModule.forRoot({}, {}),
24
+ StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: !isDevMode() }),
25
+ EffectsModule.forRoot([]),
26
  ],
27
  providers: [
28
  provideHttpClient(withInterceptorsFromDi()),
src/app/books/book-list/book-list.component.html CHANGED
@@ -7,3 +7,5 @@
7
  No books available.
8
  </li>
9
  </ul>
 
 
7
  No books available.
8
  </li>
9
  </ul>
10
+
11
+ <div class="loader" *ngIf="loading$ | async">Loading ...</div>
src/app/books/book-list/book-list.component.ts CHANGED
@@ -1,8 +1,10 @@
1
  import { Component } from '@angular/core';
 
2
  import { Observable } from 'rxjs';
3
 
4
  import { Book } from '../../shared/book';
5
- import { BookStoreService } from '../../shared/book-store.service';
 
6
 
7
  @Component({
8
  selector: 'bm-book-list',
@@ -12,8 +14,12 @@ import { BookStoreService } from '../../shared/book-store.service';
12
  })
13
  export class BookListComponent {
14
  books$: Observable<Book[]>;
 
15
 
16
- constructor(private service: BookStoreService) {
17
- this.books$ = this.service.getAll();
 
 
 
18
  }
19
  }
1
  import { Component } from '@angular/core';
2
+ import { Store } from '@ngrx/store';
3
  import { Observable } from 'rxjs';
4
 
5
  import { Book } from '../../shared/book';
6
+ import { loadBooks } from '../store/book.actions';
7
+ import { selectAllBooks, selectBooksLoading } from '../store/book.selectors';
8
 
9
  @Component({
10
  selector: 'bm-book-list',
14
  })
15
  export class BookListComponent {
16
  books$: Observable<Book[]>;
17
+ loading$: Observable<boolean>;
18
 
19
+ constructor(private store: Store) {
20
+ this.books$ = this.store.select(selectAllBooks);
21
+ this.loading$ = this.store.select(selectBooksLoading);
22
+
23
+ this.store.dispatch(loadBooks());
24
  }
25
  }
src/app/books/books.module.ts CHANGED
@@ -1,5 +1,7 @@
1
  import { NgModule } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
 
 
3
 
4
  import { BooksRoutingModule } from './books-routing.module';
5
  import { BookListComponent } from './book-list/book-list.component';
@@ -8,6 +10,8 @@ import { BookDetailsComponent } from './book-details/book-details.component';
8
  import { IsbnPipe } from './shared/isbn.pipe';
9
  import { ConfirmDirective } from './shared/confirm.directive';
10
  import { LoggedinOnlyDirective } from './shared/loggedin-only.directive';
 
 
11
 
12
  @NgModule({
13
  declarations: [
@@ -20,7 +24,9 @@ import { LoggedinOnlyDirective } from './shared/loggedin-only.directive';
20
  ],
21
  imports: [
22
  CommonModule,
23
- BooksRoutingModule
 
 
24
  ]
25
  })
26
  export class BooksModule { }
1
  import { NgModule } from '@angular/core';
2
  import { CommonModule } from '@angular/common';
3
+ import { StoreModule } from '@ngrx/store';
4
+ import { EffectsModule } from '@ngrx/effects';
5
 
6
  import { BooksRoutingModule } from './books-routing.module';
7
  import { BookListComponent } from './book-list/book-list.component';
10
  import { IsbnPipe } from './shared/isbn.pipe';
11
  import { ConfirmDirective } from './shared/confirm.directive';
12
  import { LoggedinOnlyDirective } from './shared/loggedin-only.directive';
13
+ import { BookEffects } from './store/book.effects';
14
+ import * as fromBook from './store/book.reducer';
15
 
16
  @NgModule({
17
  declarations: [
24
  ],
25
  imports: [
26
  CommonModule,
27
+ BooksRoutingModule,
28
+ StoreModule.forFeature(fromBook.bookFeatureKey, fromBook.reducer),
29
+ EffectsModule.forFeature([BookEffects])
30
  ]
31
  })
32
  export class BooksModule { }
src/app/books/store/book.actions.ts ADDED
@@ -0,0 +1,17 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createAction, props } from '@ngrx/store';
2
+
3
+ import { Book } from '../../shared/book';
4
+
5
+ export const loadBooks = createAction(
6
+ '[Book] Load Books'
7
+ );
8
+
9
+ export const loadBooksSuccess = createAction(
10
+ '[Book] Load Books Success',
11
+ props<{ data: Book[] }>()
12
+ );
13
+
14
+ export const loadBooksFailure = createAction(
15
+ '[Book] Load Books Failure',
16
+ props<{ error: string }>()
17
+ );
src/app/books/store/book.effects.alternative.ts ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { Injectable } from '@angular/core';
2
+ import { Store } from '@ngrx/store';
3
+ import { Actions, concatLatestFrom, createEffect, ofType } from '@ngrx/effects';
4
+ import { catchError, filter, map, switchMap } from 'rxjs/operators';
5
+ import { of } from 'rxjs';
6
+
7
+ import * as BookActions from './book.actions';
8
+ import { BookStoreService } from '../../shared/book-store.service';
9
+ import { selectAllBooks } from './book.selectors';
10
+
11
+ @Injectable()
12
+ export class BookEffects {
13
+
14
+ loadBooks$ = createEffect(() => {
15
+ return this.actions$.pipe(
16
+ ofType(BookActions.loadBooks),
17
+ concatLatestFrom(() => this.store.select(selectAllBooks)),
18
+ filter(([action, books]) => !books.length),
19
+ switchMap(() =>
20
+ this.service.getAll().pipe(
21
+ map(data => BookActions.loadBooksSuccess({ data })),
22
+ catchError(error => of(BookActions.loadBooksFailure({ error: error.message })))
23
+ )
24
+ )
25
+ );
26
+ });
27
+
28
+ constructor(
29
+ private actions$: Actions,
30
+ private service: BookStoreService,
31
+ private store: Store
32
+ ) {}
33
+ }
src/app/books/store/book.effects.ts ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { inject, Injectable } from '@angular/core';
2
+ import { Actions, createEffect, ofType } from '@ngrx/effects';
3
+ import { catchError, map, switchMap } from 'rxjs/operators';
4
+ import { of } from 'rxjs';
5
+
6
+ import * as BookActions from './book.actions';
7
+ import { BookStoreService } from '../../shared/book-store.service';
8
+
9
+ @Injectable()
10
+ export class BookEffects {
11
+
12
+ private actions$ = inject(Actions);
13
+ private service = inject(BookStoreService);
14
+
15
+ loadBooks$ = createEffect(() => {
16
+ return this.actions$.pipe(
17
+ ofType(BookActions.loadBooks),
18
+ switchMap(() =>
19
+ this.service.getAll().pipe(
20
+ map(data => BookActions.loadBooksSuccess({ data })),
21
+ catchError(error => of(BookActions.loadBooksFailure({ error: error.message })))
22
+ )
23
+ )
24
+ );
25
+ });
26
+ }
src/app/books/store/book.reducer.ts ADDED
@@ -0,0 +1,36 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createReducer, on } from '@ngrx/store';
2
+
3
+ import { Book } from '../../shared/book';
4
+ import * as BookActions from './book.actions';
5
+
6
+ export const bookFeatureKey = 'book';
7
+
8
+ export interface State {
9
+ books: Book[];
10
+ loading: boolean;
11
+ }
12
+
13
+ export const initialState: State = {
14
+ books: [],
15
+ loading: false
16
+ };
17
+
18
+ export const reducer = createReducer(
19
+ initialState,
20
+
21
+ on(BookActions.loadBooks, (state): State => {
22
+ return { ...state, loading: true };
23
+ }),
24
+
25
+ on(BookActions.loadBooksSuccess, (state, action): State => {
26
+ return {
27
+ ...state,
28
+ books: action.data,
29
+ loading: false
30
+ };
31
+ }),
32
+
33
+ on(BookActions.loadBooksFailure, (state, action): State => {
34
+ return { ...state, loading: false };
35
+ }),
36
+ );
src/app/books/store/book.selectors.ts ADDED
@@ -0,0 +1,16 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import { createFeatureSelector, createSelector } from '@ngrx/store';
2
+ import * as fromBook from './book.reducer';
3
+
4
+ export const selectBookState = createFeatureSelector<fromBook.State>(
5
+ fromBook.bookFeatureKey
6
+ );
7
+
8
+ export const selectBooksLoading = createSelector(
9
+ selectBookState,
10
+ state => state.loading
11
+ );
12
+
13
+ export const selectAllBooks = createSelector(
14
+ selectBookState,
15
+ state => state.books
16
+ );
src/app/books/store/test-helper.ts ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
1
+ import { Book } from '../../shared/book';
2
+
3
+ export function book(i: number): Book {
4
+ return {
5
+ isbn: 'isbn' + i,
6
+ title: 'title' + i,
7
+ authors: []
8
+ };
9
+ }