Slide 1

Slide 1 text

NgRx Component Store NgRx Component Store Rainer Hahnekamp 20.7.2022

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

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

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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: [] }); } }

Slide 9

Slide 9 text

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), })) ); }

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

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), })); } }

Slide 12

Slide 12 text

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); } }

Slide 13

Slide 13 text

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 }); }

Slide 14

Slide 14 text

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); } }

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

Alternatives Elf @rx-angular/state

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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