Angular ngrx: Difference between revisions

From bibbleWiki
Jump to navigation Jump to search
Line 92: Line 92:


==Defining State==
==Defining State==
===Module State===
To define state, we have typescript so we can be a bit better by using the interfaces not available in JS at the time
To define state, we have typescript so we can be a bit better by using the interfaces not available in JS at the time
In the module specific state we have
In the module specific state we have
Line 101: Line 102:
}
}
</syntaxHighlight>
</syntaxHighlight>
===App State===
For the app we might have wanted to do the following
For the app we might have wanted to do the following
<syntaxHighlight lang="js">
<syntaxHighlight lang="js">
Line 106: Line 108:
   products: ProductState;
   products: ProductState;
   user: any
   user: any
...
}
}
</syntaxHighlight>
</syntaxHighlight>
But this breaks the '''Lazy Loading''' as the module code is lazy loaded. The course recommended removing the products from the app state and extending the product state to include the components from the app like below.
But this breaks the '''Lazy Loading''' as the module code is lazy loaded.  
And omitting the products from the App State but leaving the users as this is not lazy loaded so it is recommended we omit the states in the app state and extend the module state to include the app state.
<syntaxHighlight lang="js">
export interface State {
  user: any
}
</syntaxHighlight>
===Extending the Module State===
Because of '''Lazy Loading''' we cannot specify all of the state for the app in the App state. In the module we may need the state of other components so to work around this we extend the state in the module to include the app state.
<syntaxHighlight lang="js">
<syntaxHighlight lang="js">
import * as AppState from '../../state/app.state';
import * as AppState from '../../state/app.state';
Line 116: Line 127:
}
}
</syntaxHighlight>
</syntaxHighlight>
And omitting the products from the App State but leaving the users as this is not lazy loaded
===Initializing the Module State===
<syntaxHighlight lang="js">
It is good practice and deterministic to initialise the state. We can do this be defining a const for use with the reducer creation
<syntaxHighlight lang="js">
<syntaxHighlight lang="js">
export interface State {
const initialState: ProductState = {
  user: any
    showProductCode: true,
    currentProduct: null,
    products: []
}
}
</syntaxHighlight>
</syntaxHighlight>

Revision as of 05:01, 5 September 2020

Introduction

ngRx is a version of redux built for Angular. It provides,

  • Centralize immutable state
  • Performance
  • Testability due to use of pure functions
  • Tooling, advance logging, visualize state tree

Redux Pattern

It provides a “unidirectional data flow” that helps to manage and organise data better and makes debugging a lot easier.

  • the UI dispatches an action to the reducer
  • the reducer sends the new state to the store
  • the store, sets the state in the reducer, and notifies the selector
  • the selector sends out new state event to all subscribed observers

  • Store, single source truth for state,
    • Don not sotr unshared status, angular state
    • Non serailizable state
  • Actions, Dispatch action to change state
  • Reducers, used to change state state, examples
    • set user details property on login,
    • toggle table a side menu visible
    • set global spinner visible property

When to Use ngRx

  • provides a place for UI state to retain ti between router views
  • provides client-side cache to use as needed
  • reducer updates the store and the store notifies all subscribers
  • it has great tooling

Sample Application Architecture

Captured this be is kinda gives an approach for small apps and where to start. Somethings it is the size of the task which can distract people from starting it.

Walkthrough a Change

Introduction

Redux follows a pattern regardless of the problem you are trying to solve. This may look like the React Redux Page after I have finished.

Process

We are going to update the display product code state.

  • User issues a click
  • The component dispatches an action to the reducer
  • The reducer uses the action as input and the current state from the store to create new state
  • The reducer sends the state to the store
  • The store broadcasts the state to all subscribers, in this case the component
  • Component updates it's bound property

Installing Packages

Initializing the Store

Installing

Using the angular cli app.modules.ts is updated along with packages.json

ng add @ngrx/store

This makes the following changes

import { StoreModule } from '@ngrx/store';
...
@NgModule({
  imports: [
    BrowserModule,
...
    StoreModule.forRoot({}, {})
  ],

Module Considerations

For modules, like the router module, there is a forFeature equivalent of the StoreModule. We can, just like react have multiple reducers for a feature too. Each reducer is stored using tag/value.

    StoreModule.forFeature('products', {
       'productList': listReducer,
       'productData': dataReducer
    })

Installing DevTools

First install the tools

ng add @ngrx/store-devtools

Initialize in the app module with

import { StoreDevtoolsModule } from '@ngrx/store-devtools';
...
@NgModule({
  imports: [
    BrowserModule,
...
    StoreDevtoolsModule.instrument({
        name: 'Application Name to Recognise'
        maxAge: 25,
        logOnly: environment.production
    })
  ],

Defining State

Module State

To define state, we have typescript so we can be a bit better by using the interfaces not available in JS at the time In the module specific state we have

export interface ProductState {
   showProductCode: boolean;
   currentProduct: Product;
   products: Product[]
}

App State

For the app we might have wanted to do the following

export interface State {
   products: ProductState;
   user: any
...
}

But this breaks the Lazy Loading as the module code is lazy loaded. And omitting the products from the App State but leaving the users as this is not lazy loaded so it is recommended we omit the states in the app state and extend the module state to include the app state.

export interface State {
   user: any
}

Extending the Module State

Because of Lazy Loading we cannot specify all of the state for the app in the App state. In the module we may need the state of other components so to work around this we extend the state in the module to include the app state.

import * as AppState from '../../state/app.state';

export interface State extends AppState.State {
    products: ProductState
}

Initializing the Module State

It is good practice and deterministic to initialise the state. We can do this be defining a const for use with the reducer creation
<syntaxHighlight lang="js">
const initialState: ProductState = {
    showProductCode: true,
    currentProduct: null,
    products: []
}

Defining Actions

This code defines an action on the fly.

Building a Reducer

The function return a copy of the state not a modified state by ... (spreading) the original state and adding or replacing the new state.

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

export const productReducer = createReducer<ProductState>(
    {showProductCode: false} as ProductState,
    on(createAction('TEST'), (state) : ProductState => {
        return {
            ...state,
            showProductCode: !state.showProductCode
        }
    })
)

Do not forget to update the module to have the reducer assigned.

...
    StoreModule.forFeature('products', productReducer)
...

Dispatching an Action

We need to inject the store into the component and dispatch the action. We do this using dependency injection and for the store and the Angular checkChanged to to initiate the dispatch. Note We are using the product definition of State and not the App definition as it will lack the product reducer definition.

...
import { State } from '../state/product.reducer';

...
  constructor(
    private productService: ProductService,
    private store: Store<State>) { }
..
  checkChanged(): void {
    this.store.dispatch({
      type:
      '[Product] toggle product code'})
  }

Subscribing to the Store

We subscribe to the store using the name we used in the module. This can be done in the ngOnInit function for Angular. The products is the name we used when initializing the store the module code.

    this.store.select('products').subscribe(
      products => {
        if(products) {
          this.displayCode = products.showProductCode
        }
      }
    )