Thought Leadership
Sep 28, 2022

Component Store 101 — Main concepts and @ngrx/store Interactions

Make this standalone library work to your advantage

Component Store 101 — Main concepts and @ngrx/store Interactions

Introduction

Component store is a stand-alone library that allows you to manage local component state reactively. This library can be used alongside NgRx, and it is a great solution for moving state that can be managed locally out of the global store.

In this article you will learn the basic concepts of the component store library, as well as how to interact with the NgRx Store. Here is a comparison of both stores.

The @ngrx/component-store can be instantiated multiple times. Each instance has its own independent state and it is cleaned up when its parent component is destroyed.

This article has to-the-point code snippets, it assumes that you have a general understanding of both Angular and NgRx. These snippets serve as a quick reference to @ngrx/component-store architecture, methods and syntax.

About the Code Example

The example consists of global store, global actions, and global selectors at the application level.

The component using the @ngrx/component-store is a mat-selection-list. This component uses an effect to load a list of characters. In addition, it selects and unselects characters. Finally, it interacts with the @ngrx/store by dispatching a global action in order to toggle a sidenav and by selecting the sidenav status, both from @ngrx/store.

The most important piece of code is the character-selection.store.ts file inside the character-selection folder because it contains the component’s state. Note that the html code for this example is not of relevance.

🌟 Full code: https://stackblitz.com/edit/component-store-101

Component Store Initialization

The component’s store is an injectable class, and it must extend ComponentStore. The initial state is set in the constructor.

import { Injectable } from '@angular/core';
import { ComponentStore } from '@ngrx/component-store';

export interface CharacterSelectionState {
  collection: any[];
  selectedCharacterIds: number[];
  isLoadingCollection: boolean;
  error: HttpErrorResponse | null;
}

export const initialState: CharacterSelectionState = {
  collection: [],
  selectedCharacterIds: [],
  isLoadingCollection: false,
  error: null,
};

@Injectable()
export class CharacterSelectionStore extends ComponentStore<CharacterSelectionState> {
  
  constructor(private charactersService: CharactersService) {
    super(initialState);
  }
}

Reading State from Component Store

The ComponentStore class has a select method for reading the state, and it returns an observable.

characters$ = this.select((state) => state.collection);

🌟 You can combine selectors and/or Observables in order to create a new selector.

private selectedCharacterIds$ = this.select(
    (state) => state.selectedCharacterIds
  );

  characters$ = this.select((state) => state.collection);
  selectedCharacters$ = this.select(
    this.characters$,
    this.selectedCharacterIds$,
    (characters, selectedCharacterIds) =>
      selectedCharacterIds.map((id) =>
        characters.find((character) => character._id === id)
      )
  );

The above selector combines two other selectors characters$ and selectedCharacterIds$ in order to return the selected character objects as an observable in the new selectedCharacters$ selector.

Updating the Component Store State

The state can be updated in three ways:

  • setState — Can be called by either providing the object of state type or as a callback.
  • patchState — Can be called by providing a partial state object or a partial updater callback.
  • Creating an updater and passing inputs through it — Describes HOW the state changes.

setState

resetSelectedCharacters() {
    this..setState({selectedCharacterIds: []});
  }

selectCharacter(characterId: number): void {
    this.setState((state) => {
      return {
        ...state,
        selectedCharacterIds: [...state.selectedCharacterIds, characterId],
      };
    });
  }

patchState

selectCharacter(characterId: number): void {
    this.patchState({ currentCharacterId: characterId });
}

Updater Method

readonly addCharacter = this.updater((state, character: Character) => ({
    collection: [...state.collection, character],
  }));

🌟 The updater can take an observable or it can be called with the values imperatively.

add(movie: string) {
    this.characterSelectionStore.addCharacter({ name: 'Mufasa', id: 100 });
}

Component Store Effects

The select character component will use an effect to load the characters from a service:

readonly loadCharacters = this.effect(
    (
      params$: Observable<{
        apiToken: string;
        searchTerm?: string;
      }>
    ) => {
      return params$.pipe(
        switchMap((params) => {
          if (params.searchTerm) {
            this.setState((state) => {
              return {
                ...state,
                error: null,
                isLoadingCollection: true,
              };
            });
          }

          return this.charactersService.getCharacters().pipe(
            tapResponse(
              (characters) => {
                console.log(characters);
                this.setState((state) => {
                  return {
                    ...state,
                    collection: characters,
                    isLoadingCollection: false,
                  };
                });
              },
              (error: HttpErrorResponse) => {
                this.setState((state) => {
                  return {
                    ...state,
                    error,
                    isLoadingCollection: false,
                  };
                });
              }
            )
          );
        })
      );
    }
  );

The tapResponse allows handling a component store effect response with ease and without any additional boilerplate.

Using The Component Store

Add the CharacterSelectionStore in the component’s providers and inject it in the constructor like a service:

@Component({
  // ... other metadata
  providers: [CharacterSelectionStore]
})
export class BooksPageComponent {
  constructor(private characterSelectionStore: CharacterSelectionStore) {}
}

Selecting Data

characters$ = this.characterSelectionStore.characters$;

Using Effects

ngOnInit(): void {
    this.characterSelectionStore.loadCharacters({ apiToken: 'an api token' });
  }

Interacting with @ngrx/store

The @ngrx/component-store can interact with the @ngrx/store by importing and injecting Store in the component store’s constructor. You can select from the @ngrx/store by selector. It is also possible to dispatch actions.

import { GlobalActions } from '../+state/global.actions';... 
import { selectSideNavStatus } from '../+state/global.selectors';

@Injectable()
export class CharacterSelectionStore extends ComponentStore<CharacterSelectionState> {
  sideNavStatus$ = this.store.select(selectSideNavStatus);

  constructor(private store: Store ) {
    ...
  }


	toggleDetail(): void {
    this.store.dispatch(GlobalActions.toggleDetailSidenav());
  }
	...

}

Conclusion

  • @ngrx/component-store Is a great solution for moving any local state that can be managed locally from @ngrx/store.
  • The @ngrx/component-store can interact and coexist with the @ngrx/store. Both are independent libraries.
  • @ngrx/component-store does not have inner actions, but it can dispatch actions from @ngrx/store.
  • You can create multiple @ngrx/component-store instances.

. . .
Article Summary
Component store is a stand-alone library that allows you to manage local component state reactively. This library can be used alongside NgRx, and it is a great solution for moving state that can be managed locally out of the global store.
Author
HeroDevs
Thought Leadership
Related Articles
Open Source Insights Delivered Monthly

By clicking “submit” I acknowledge receipt of our Privacy Policy.

Thanks for signing up for our Newsletter! We look forward to connecting with you.
Oops! Something went wrong while submitting the form.