$30 off During Our Annual Pro Sale. View Details »

NgRx Component Store

NgRx Component Store

Slides for my talk on NgRx Component Store

Rainer Hahnekamp

July 20, 2022
Tweet

More Decks by Rainer Hahnekamp

Other Decks in Technology

Transcript

  1. NgRx Component Store
    NgRx Component Store
    Rainer Hahnekamp
    20.7.2022

    View Slide

  2. About Me...
    ● Rainer Hahnekamp
    ANGULARarchitects.io
    ● Trainings and Consulting
    @RainerHahnekamp
    Professional NgRx
    https://www.ng-news.com
    https://www.youtube.com
    /c/RainerHahnekamp

    View Slide

  3. Agenda
    ● NgRx Family
    ● Theory
    ● Selectors
    ● Actions
    ● Effects
    ● Miscellaneous

    View Slide

  4. NgRx Family
    Store
    Effects
    Entity
    Data
    Router Store
    Store Devtools
    Schematics
    ESLint Plugin
    Component
    Store
    Component

    View Slide

  5. Main facts
    ● (Little) sibling to @ngrx/store
    ● Local / component state management
    ● State vanishes with component (by default)
    ● Push-based
    ● All logic included in single service

    View Slide

  6. Use cases
    ● Complicated, local state logic
    ● Multiple instances of same components
    ● Different static logic (Decoupling, Shared)
    ● ~Simplified, global state

    View Slide

  7. API
    ComponentStore
    SideEffects
    Read Write
    select(): Observable
    patchState()
    setState()
    updater() // like reducer
    effect()

    View Slide

  8. Defining the state
    export interface HolidaysState {
    holidays: Holiday[];
    favouriteIds: number[];
    }
    @Injectable()
    export class HolidaysStore extends ComponentStore {
    constructor(private httpClient: HttpClient, private config: Configuration) {
    super({ holidays: [], favouriteIds: [] });
    }
    }

    View Slide

  9. Selectors
    export interface HolidaysState {
    holidays: Holiday[];
    favouriteIds: number[];
    }
    @Injectable()
    export class HolidaysStore extends ComponentStore {
    constructor(private httpClient: HttpClient, private config: Configuration) {
    super({ holidays: [], favouriteIds: [] });
    }
    readonly holidays$ = this.select(({ holidays, favouriteIds }) =>
    holidays.map((holiday) => ({
    ...holiday,
    isFavourite: favouriteIds.includes(holiday.id),
    }))
    );
    }

    View Slide

  10. Using the ComponentStore
    @Component({
    templateUrl: './holidays.component.html',
    standalone: true,
    imports: [...],
    providers: [HolidaysStore],
    })
    export class HolidaysComponent {
    holidays$ = this.holidaysStore.holidays$;
    constructor(private holidaysStore: HolidaysStore) {
    }
    }

    View Slide

  11. Actions
    @Injectable()
    export class HolidaysStore extends ComponentStore {
    // ...
    addFavourite(holidayId: number) {
    this.patchState((state) => ({
    favouriteIds: [...state.favouriteIds, holidayId],
    }));
    }
    removeFavourite(holidayId: number) {
    this.patchState((state) => ({
    favouriteIds: state.favouriteIds.filter((id) => id !== holidayId),
    }));
    }
    }

    View Slide

  12. Using the ComponentStore
    @Component({
    templateUrl: './holidays.component.html',
    standalone: true,
    imports: [...],
    providers: [HolidaysStore],
    })
    export class HolidaysComponent {
    holidays$ = this.holidaysStore.holidays$;
    constructor(private holidaysStore: HolidaysStore) { }
    addFavourite(id: number) {
    this.holidaysStore.addFavourite(id);
    }
    removeFavourite(id: number) {
    this.holidaysStore.removeFavourite(id);
    }
    }

    View Slide

  13. Effects
    @Injectable()
    export class HolidaysStore extends ComponentStore {
    // ...
    readonly load = this.effect((i$: Observable) => {
    return i$.pipe(
    switchMap(() =>
    this.httpClient.get(this.#baseUrl).pipe(
    tapResponse((holidays) => {
    const finalHolidays = holidays.map((holiday) => ({
    ...holiday,
    imageUrl: `${this.config.baseUrl}${holiday.imageUrl}`,
    }));
    this.#setHolidays(finalHolidays);
    }, console.error)
    )
    )
    );
    });
    #setHolidays = (holidays: Holiday[]) => this.patchState({ holidays });
    }

    View Slide

  14. Using the ComponentStore
    @Component({
    templateUrl: './holidays.component.html',
    standalone: true,
    imports: [...],
    providers: [HolidaysStore],
    })
    export class HolidaysComponent {
    holidays$ = this.holidaysStore.holidays$;
    constructor(private holidaysStore: HolidaysStore) {
    this.holidaysStore.load();
    }
    addFavourite(id: number) {
    this.holidaysStore.addFavourite(id);
    }
    removeFavourite(id: number) {
    this.holidaysStore.removeFavourite(id);
    }
    }

    View Slide

  15. Comparison to store
    ● Same reactive behaviour
    ● No devtools
    ● Scalability issues
    ● Simpler
    ● Nice goodies
    ○ patchState
    ○ Debounced selectors

    View Slide

  16. Also keep in mind
    ● Disposing of resources is built-in
    ● Combination with @ngrx/store possible
    ● Can also manage global state
    ○ via {providedIn: 'root'}

    View Slide

  17. When to use?
    ● Instead of services based on BehaviorSubject
    ● Non-global state
    ● Early phases of application development

    View Slide

  18. Alternatives
    Elf @rx-angular/state

    View Slide

  19. Summary
    ● Simple State Management
    ● Reactive Behaviour
    ● Easy to upgrade to NgRx Store later

    View Slide

  20. Further Reading/Watching
    ● Original Design Document
    ○ https://hackmd.io/zLKrFIadTMS2T6zCYGyHew?view
    ● Official Documentation
    ○ https://ngrx.io/guide/component-store
    ● Alex Okrushko on Component Store
    ○ https://www.youtube.com/watch?v=v5WSUE1_YHM

    View Slide