Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Redux with GraphQL integration

fr0gM4ch1n3
September 25, 2018

Redux with GraphQL integration

fr0gM4ch1n3

September 25, 2018
Tweet

More Decks by fr0gM4ch1n3

Other Decks in Education

Transcript

  1. Consider using Redux You should when: • multiple components needs

    to access the same state but do not have any parent/child relationship • you start to feel awkward passing down the state to multiple components with props Dan Abramov says “Flux libraries are like glasses: you’ll know when you need them.”
  2. Consider using Redux And in fact it worked like that

    for me. Be aware that Redux is not useful for smaller apps. It really shines in bigger ones.
  3. @ngrx - state export interface State { id: number; project:

    number; user: number; start: number; end: number; } const initialState: State = { id: undefined, project: undefined, user: undefined, start: undefined, end: undefined }; Refers to the single state value that is managed by the store
  4. @ngrx - actions The principle of Redux says the only

    way to change the state is by sending a signal to the store Actions must have a type field that indicates the type of action being performed
  5. @ngrx - actions import { Action } from '@ngrx/store'; export

    const START = '[Entry] start timer'; export const STARTED = '[Entry] timer started'; export class Start implements Action { readonly type = START; constructor(public payload: { project: number }) { } } export class Started implements Action { readonly type = STARTED; constructor(public payload: Entry) { } } export type All = Start | Started;
  6. @ngrx - actions import { Action } from '@ngrx/store'; export

    const START = '[Entry] start timer'; export const STARTED = '[Entry] timer started'; export class Start implements Action { readonly type = START; constructor(public payload: { project: number }) { } } export class Started implements Action { readonly type = STARTED; constructor(public payload: Entry) { } } export type All = Start | Started;
  7. @ngrx - selectors import { createSelector } from '@ngrx/store'; import

    { IDB } from '../IDB.interface'; function getEntryState(db: IDB): Entry[] { return db.entryBranch; } export const getAll = createSelector( getEntryState, (state: Entry[]) => state ); export const getActive = createSelector( getEntryState, (state: Entry[]) => state.filter(entry => !entry.end) );
  8. @ngrx - reducers Reducers produce the state of the application

    A reducer is just a pure function. A reducer takes two parameters: the current state and an action const rootReducer = (state = initialState, action) => state;
  9. @ngrx - reducers export function EntryReducer(state = initialEntryBranch, action: EntryActions.All):

    EntryState { switch (action.type) { case EntryActions.START: { return { ...state, ...action.payload }; } case EntryActions.STARTED: { return { ...state, ...action.payload }; } default: { return state; } } }
  10. @ngrx - reducer export function EntryReducer(state = initialEntryBranch, action: EntryActions.All):

    EntryState { switch (action.type) { case EntryActions.START: { return { ...state, ...action.payload }; } case EntryActions.STARTED: { return { ...state, ...action.payload }; } default: { return state; } } } switch (action.type) { case EntryActions.START: { } case EntryActions.STARTED: { } default: { } }
  11. @ngrx - reducer export function EntryReducer(state = initialEntryBranch, action: EntryActions.All):

    EntryState { switch (action.type) { case EntryActions.START: { return { ...state, ...action.payload }; } case EntryActions.STARTED: { return { ...state, ...action.payload }; } default: { return state; } } } return { ...state, ...action.payload }; return { ...state, ...action.payload }; return state;
  12. @ngrx - effects ACTION 1 REDUCER 1 STATE ACTION 2

    REDUCER 2 STATE EFFECT 1 API time
  13. @ngrx - effects import * as entry from './entry.actions'; import

    { EntryResource } from './entry.resource'; @Injectable() export class EntryEffects { @Effect() start$: Observable<Action> = this.actions$.pipe( ofType<entry.Start>(entry.START), switchMap(({ payload }) => this.er.start(payload).pipe( map(payload => new entry.Started(payload)), catchError(err => of(new entry.Error(err))) ) ) ); constructor(private actions$: Actions, private er: EntryResource) { } }
  14. @ngrx - effects import * as entry from './entry.actions'; import

    { EntryResource } from './entry.resource'; @Injectable() export class EntryEffects { @Effect() start$: Observable<Action> = this.actions$.pipe( ofType<entry.Start>(entry.START), switchMap(({ payload }) => this.er.start(payload).pipe( map(payload => new entry.Started(payload)), catchError(err => of(new entry.Error(err))) ) ) ); constructor(private actions$: Actions, private er: EntryResource) { } } this.er.start(payload).pipe( map(payload => new entry.Started(payload)), catchError(err => of(new entry.Error(err))) )
  15. GraphQL subscribe queryRef.subscribeToMore({ document: gql` subscription entryChanged { entryChanged {

    id data } } `, onError: console.error, updateQuery: (prev, { subscriptionData }) => subscriptionData.data ? { ...prev, entries: [ ...prev.entries.filter((entry: Entry) => entry.id !== subscriptionData.data.entryChanged.id), subscriptionData.data.entryChanged ] } : prev });
  16. Setup ngrx cache @NgModule({ imports: [ StoreModule.forRoot({ apollo: apolloReducer, }),

    NgrxCacheModule, ], }) class AppModule { constructor(ngrxCache: NgrxCache) { const cache = ngrxCache.create({}); } } Put apolloReducer under apollo in your State and import NgrxCacheModule
  17. How to use Just run your GraphQL queries in effects

    and receive data asynchronously from store automatically
  18. How to use function getEntryState(db: IDB): Entry[] { return ((db.apollo.ROOT_QUERY

    || {}).entries || []) .map((ref: RootQuery) => ref.id) .map((id: string) => db.apollo[id]); } export const getAll = createSelector( getEntryState, state => state ) Minor changes in selectors
  19. How to use function getEntryState(db: IDB): Entry[] { return ((db.apollo.ROOT_QUERY

    || {}) .entries || []) .map((ref: RootQuery) => ref.id) .map((id: string) => db.apollo[id]); } export const getAll = createSelector( getEntryState, state => state ); Minor changes in selectors
  20. How to use import { Store } from '@ngrx/store'; import

    { IDB } from '../state-management/IDB.interface'; import * as entry from '../state-management/entry/entry.actions'; import * as fromEntry from '../state-management/entry/entry.reducer'; export class EntriesComponent { public entries$: Observable<Entry[]>; constructor( private store: Store<IDB>) { this.entries$ = this.store.select(fromEntry.getAll); this.store.dispatch(new entry.Load()); } } <ul> <li *ngFor="let entry of entries$ | async">{{ entry | json }}</li> </ul>
  21. How to use import { Store } from '@ngrx/store'; import

    { IDB } from '../state-management/IDB.interface'; import * as entry from '../state-management/entry/entry.actions'; import * as fromEntry from '../state-management/entry/entry.reducer'; export class EntriesComponent { public entries$: Observable<Entry[]>; constructor( private store: Store<IDB>) { this.entries$ = this.store.select(fromEntry.getAll); this.store.dispatch(new entry.Load()); } } <ul> <li *ngFor="let entry of entries$ | async">{{ entry | json }}</li> </ul>