Showing posts with label Redux. Show all posts
Showing posts with label Redux. Show all posts

Thursday, 16 November 2023

Angular redux with NGRX

 Angular redux (state management)


action/effects/reducer/store(state,selector)


offcial documentation :https://ngrx.io/guide/store/selectors


version (https://ngrx.io/docs):

Version 14 has the minimum version requirements:

Angular version 14.x

Angular CLI version 14.x

TypeScript version 4.6.x

RxJS version ^6.5.3 || ^7.5.0


Version 12 has the minimum version requirements:

Angular version 12.x

Angular CLI version 12.x

TypeScript version 4.2.x

RxJS version 6.5.x

V7 has the minimum version requirements:

Angular version 7

TypeScript version 3.1.x

RxJS version 6.x


actions

https://ngrx.io/guide/store/actions

Action Interface


interface Action {

  type: string;

}


login-page.actions.ts

import { createAction, props } from '@ngrx/store';


export const login = createAction(

  '[Login Page] Login',

  props<{ username: string; password: string }>()

);


login-page.component.ts

onSubmit(username: string, password: string) {

  store.dispatch(login({ username: username, password: password }));

}



https://codeburst.io/angular-10-ngrx-store-by-example-333cbf16862c

* Action can just be dispatched as an object



// src/app/product/product.component.ts

import { Product } from './product.model';

import { AppState } from './../app.state';

import { Component, OnInit } from '@angular/core';

import { Store } from '@ngrx/store';

@Component({

  selector: 'app-product',

  templateUrl: './product.component.html',

  styleUrls: ['./product.component.css']

})

export class ProductComponent implements OnInit {

  products: Observable<Product[]>;

  constructor(private store: Store<AppState>) {

this.products = this.store.select(state => state.product);

   }

  addProduct(name, price) {

this.store.dispatch({

  type: 'ADD_PRODUCT',

  payload: <Product> {

name: name,

price: price

  }

});

  }

  ngOnInit() {

  }

}



reducers

https://ngrx.io/guide/store/reducers

*handles action, save the state in store 


scoreboard-page.actions.ts


import { createAction, props } from '@ngrx/store';


export const homeScore = createAction('[Scoreboard Page] Home Score');

export const awayScore = createAction('[Scoreboard Page] Away Score');

export const resetScore = createAction('[Scoreboard Page] Score Reset');

export const setScores = createAction('[Scoreboard Page] Set Scores', props<{game: Game}>());


scoreboard.reducer.ts


import { Action, createReducer, on } from '@ngrx/store';

import * as ScoreboardPageActions from '../actions/scoreboard-page.actions';


export interface State {

  home: number;

  away: number;

}

export const initialState: State = {

  home: 0,

  away: 0,

};

export const scoreboardReducer = createReducer(

  initialState,

  on(ScoreboardPageActions.homeScore, state => ({ ...state, home: state.home + 1 })),

  on(ScoreboardPageActions.awayScore, state => ({ ...state, away: state.away + 1 })),

  on(ScoreboardPageActions.resetScore, state => ({ home: 0, away: 0 })),

  on(ScoreboardPageActions.setScores, (state, { game }) => ({ home: game.home, away: game.away }))

);

!!! Register reducer, reducer is registered in store

app.module.ts

import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';

import { scoreboardReducer } from './reducers/scoreboard.reducer';


@NgModule({

  imports: [

StoreModule.forRoot({ game: scoreboardReducer })

  ],

})

export class AppModule {}

--------------------

register reducer as feature :

!!! Register reducer, reducer is registered in store


app.module.ts


import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';


@NgModule({

  imports: [

StoreModule.forRoot({})

  ],

})

export class AppModule {}

scoreboard.reducer.ts


export const scoreboardFeatureKey = 'game';

scoreboard.module.ts


import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';

import { scoreboardFeatureKey, scoreboardReducer } from './reducers/scoreboard.reducer';

  

@NgModule({

  imports: [

StoreModule.forFeature(scoreboardFeatureKey, scoreboardReducer)

  ],

})

export class ScoreboardModule {}


stores(state, selectors)


https://codeburst.io/angular-10-ngrx-store-by-example-333cbf16862c


1) define state :


src/app/app.state.ts

// src/app/app.state.ts

import { Product } from './product/product.model';

export interface AppState {

  readonly product: Product[];

}

2) Import state and store in component 

(!!! reducer is registered in store at app module)

// src/app/product/product.component.ts

import { Product } from './product.model';

import { AppState } from './../app.state';

import { Component, OnInit } from '@angular/core';

import { Store } from '@ngrx/store';

@Component({

  selector: 'app-product',

  templateUrl: './product.component.html',

  styleUrls: ['./product.component.css']

})

export class ProductComponent implements OnInit {

  products: Observable<Product[]>;

  constructor(private store: Store<AppState>) {

this.products = this.store.select(state => state.product);

   }



selectors:

https://ngrx.io/guide/store/selectors

(to select some piece of state instead of returning all state)

index.ts


import { createSelector } from '@ngrx/store';

 

export interface User {

  id: number;

  name: string;

}

 

export interface Book {

  id: number;

  userId: number;

  name: string;

}

 

export interface AppState {

  selectedUser: User;

  allBooks: Book[];

}

 

export const selectUser = (state: AppState) => state.selectedUser;

export const selectAllBooks = (state: AppState) => state.allBooks;

 

export const selectVisibleBooks = createSelector(

  selectUser,

  selectAllBooks,

  (selectedUser: User, allBooks: Book[]) => {

if (selectedUser && allBooks) {

  return allBooks.filter((book: Book) => book.userId === selectedUser.id);

} else {

  return allBooks;

}

  }

);


addtional selector example (https://www.codemag.com/article/1811061/Angular-and-the-Store)

export const getAllDevelopers =

createSelector(getState, (state): Developer[] => {

return state && state.developers;

}

);

this.store.select<Developer[]>(getAllDevelopers).subscribe(

developers => console.log(developers)

);


effects 


https://v7.ngrx.io/guide/effects


!!! effects just pipe on action, do an api call, map the result into new action to handle by reducer !!!!


// Without effects 

movies-page.component.ts


@Component({

  template: `

<li *ngFor="let movie of movies">

  {{ movie.name }}

</li>

  `

})

export class MoviesPageComponent {

  movies: Movie[];

 

  constructor(private movieService: MoviesService) {}

 

  ngOnInit() {

this.movieService.getAll().subscribe(movies => this.movies = movies);

  }

}


movies.service.ts


@Injectable({

  providedIn: 'root'

})

export class MoviesService {

  constructor (private http: HttpClient) {}


  getAll() {

return this.http.get('/movies');

  }

}

// with effects 

movies-page.component.ts


@Component({

  template: `

<div *ngFor="let movie of movies$ | async">

  {{ movie.name }}

</div>

  `

})

export class MoviesPageComponent {

  movies$: Observable = this.store.select(state => state.movies);

  constructor(private store: Store<{ movies: Movie[] >}) {}

  ngOnInit() {

this.store.dispatch({ type: '[Movies Page] Load Movies' });

  }

}

movie.effects.ts


import { Injectable } from '@angular/core';

import { Actions, Effect, ofType } from '@ngrx/effects';

import { of } from 'rxjs';

import { map, mergeMap } from 'rxjs/operators';

 

@Injectable()

export class MovieEffects {

 

  @Effect()

  loadMovies$ = this.actions$

.pipe(

  ofType('[Movies Page] Load Movies'),

  mergeMap(() => this.moviesService.getAll()

.pipe(

  map(movies => ({ type: '[Movies API] Movies Loaded Success', payload: movies })),

  catchError(() => of({ type: '[Movies API] Movies Loaded Error' }))

))

  )

);

 

  constructor(

private actions$: Actions,

private moviesService: MoviesService

  ) {}

}


Register effects at root 


app.module.ts


import { EffectsModule } from '@ngrx/effects';

import { MovieEffects } from './effects/movie.effects';


@NgModule({

  imports: [

EffectsModule.forRoot([MovieEffects])

  ],

})

export class AppModule {}