Dall'archivio articoli > Angular
Utilizzare Redux in applicazioni Angular con la libreria NgRx
Per poter utilizzare questa funzionalità, devi fare il login o iscriverti.
Con l'avvento dei framework Client side, e principalmente grazie a React, è sorta la necessità di salvare i dati scaricati dal Web Server per poterli poi riutilizzare in più punti dell'applicazione, eseguire operazioni di aggiornamento, diramare l'effetto dell'aggiornamento e visualizzare immediatamente queste modifiche in tutte le aree dell'applicazione. L'insieme di tutti i meccanismi che forniscono queste funzionalità vengono chiamati State management.
Le prime versioni di React sono state implementate su di una architettura chiamata Flux, trasformatosi poi in Redux: Flux + Functional programming, questa evoluzione è dovuta alla natura 'one-way' della libreria Flux i cui oggetti e meccanismi interni di distribuzione aggiornamenti non combaciavano perfettamente con React, ma procediamo con calma...
Lo state management pattern di compone di 3 elementi cardini Action, Store e Reducer.
All'interno di React il Redux pattern è alla base del sistema di comunicazione tra le varie views, ma come sappiamo il framework Angular ha un buon livello di complessità ed è strutturato su vari layer: componenti, direttive, servizi, interceptor ecc.. Inserire altra complessità utilizzando uno store management potrebbe non essere una buona scelta architetturale e impone al team, o a chiunque andrà poi a manuntenere il codice, di conoscere questo pattern di programmazione. Può essere utile analizzare in anticipo quando possiamo trarre benefici da questo pattern.
E' sicuramente da evitare quando:
Di contro è consigliato quando:
Per poter ricostruire il Redux pattern avremo bisogno di un libreria: NgRx.La gestione dello store e la comunicazioni tra le parti è stata creata tramite RxJS ed è quindi pienamente compatibile ed interagibile tramite Angular. Rispetta appieno i cardini del Redux pattern apportando qualche modifica che semplifica il ruolo del developer:
Come tutti i pacchetti npm l'installazione può avvenire tramite il comando
npm install @ngrx/store --save
se il progetto utilizza una CLI versione 6+ possiamo usare la funzionalità ng per installare ed inserire all'interno dell'app.module.ts gli import necessari.
ng add @ngrx/store
per la demo che svilupperemo in questo articolo avremo anche bisogno di queste librerie
npm install @ngrx/effects @ngrx/entity --save
che rispettivamente consentiranno di modificare lo state sulla base di eventi esterni e ci consentiranno di avere degli helper per creare/modificare/ottenere le entità salvate nello state.
Esiste anche un set di utilities che ci possono tornare utili durante lo sviluppo, installabili tramite
npm install @ngrx/store-devtools --save
NgRx fornisce anche un set di schematics per la creazione dei boilerplace, questi dovranno, dopo essere stati scaricati, impostati come schematics di default del progetto Angular
npm install @ngrx/schematics --save-dev ng config cli.defaultCollection @ngrx/schematics
Gli schematics classici di angular non verranno persi perchè @ngrx/schematics estende @schematics/angular. Per controprova, nel file angular.json dovremmo avere questa sezione
"schematics": { "@ngrx/schematics:component": { "styleext": "scss" } }
Procediamo alla creazione di un modello per la nostra applicazione
export interface Book { bookId: string; title: string; description: string; }
Le action sono gli eventi che avvengono nella nostra applicazione. Nel caso di una pagina in cui dobbiamo caricare una lista di libri, una action può essere load books, verrà quindi eseguito un dispatch dell'azione.
E' importante specificare che ogni azione sarà responsabile di un possibile cambio di state, quindi l'azione di load book potrà impostare una proprietà loading a true all'interno dello state, allo stesso modo, quando il load sarà terminato verrà scatenata un'azione, load book success che inserirà i libri caricati nello store e imposterà la proprietà loading a false.
Da questa descrizione si può evincere come ogni action, il cui ruolo sarà interrogare un servizio, avrà due azioni correlate una success ed una error/fail.
Incominciamo a strutturare le action partendo dalla definizione dei tipi di azione all'interno di un enumeratore.
export enum BookActionTypes { LOAD_BOOKS = '[Book] Load books', LOAD_BOOKS_SUCCESS = '[Book] Load books success', LOAD_BOOKS_ERROR = '[Book] Load books error' }
I valori inseriti nell'enumeratore verranno usati come identificatori univoci dell'azione, sarà quindi nostra premura verificare che non vi siano mai due azioni con lo stesso valore.
Proseguiamo inserendo le azioni. Queste sono delle classi che implementano l'interfaccia Action di @ngrx/store, hanno una proprietà che definisce il tipo di azione, e possono avere dei parametri nel costruttore che per convenzione vengono wrappati in una proprietà definita payload.
export class LoadBooks implements Action { readonly type = BookActionTypes.LOAD_BOOKS; constructor() { } } export class LoadBooksSuccess implements Action { readonly type = BookActionTypes.LOAD_BOOKS_SUCCESS; constructor(public payload: { books: Book[] }) { } } export class LoadBooksError implements Action { readonly type = BookActionTypes.LOAD_BOOKS_ERROR; constructor(public payload: { error: any }) { } }
Per poter utilizzare il type inference nel reducer sarà necessario creare un tipo che conterrà l'unione di tutte le action presenti.
export type BookActionsUnion = | LoadBooks | LoadBooksSuccess | LoadBooksError;
Il codice completo è disponibile nell'allegato a questo articolo.
Come già anticipato, all'interno del reducer andremo ad implementare tutti i cambiamenti che deve subire lo state a seguito di un dispatch. Dato che con il Redux pattern vi è la possibilità di avere più state che verranno convogliati in uno più grande, possiamo definire uno state apposito per i libri e scrivere un reducer ad-hoc.
La prima fase sarà quella di creare un modello per lo state e definirne il valore di default.
export interface State { isLoading: boolean; } export const initialState: State = { isLoading: false };
Successivamente possiamo definire un reducer che, basandosi su questo state, possa modificarlo a seconda dell'azione.
export function reducer(state = initialState, action: BookActionsUnion): State { switch (action.type) { case BookActionTypes.LOAD_BOOKS: { return { ...state, isLoading: true }; } case BookActionTypes.LOAD_BOOKS_SUCCESS: { return { ...state, isLoading: false }; } case BookActionTypes.LOAD_BOOKS_ERROR: { return { ...state, isLoading: false }; } default: return state; } }
In questo esempio all'interno del case LOAD_BOOKS_SUCCESS avremmo dovuto utilizzare una proprietà books all'interno del payload, al contempo, all'interno dello state avremmo dovuto avere una proprietà books: Book[] per il savataggio dei libri proveniente dal servizio. Ciò non è necessario perchè in NgRx abbiamo a disposizione un pacchetto (@ngrx/entity) che conterrà tutti gli helper e le ottimizzazioni necessarie per eseguire operazioni su liste di oggetti.
Il codice completo è disponibile nell'allegato a questo articolo.
Attenzione: Questo articolo contiene un allegato.
Per inserire un commento, devi avere un account.
Fai il login e torna a questa pagina, oppure registrati alla nostra community.