What is @ngrx/component-store? Introduction to @ngrx
Introduction to @ngrx/component-store
Angular has become one of the popular frontend development platforms that came into the limelight. With its frequent updates, you can find the latest features and extended library support which help in enticing frontend developments. Few among these libraries would assist us in understanding various subjects like behaviour subject of Angular, async subject, etc. In this blog, we will learn about the component-store library and its significance.
What is @ngrx/component-store?
Component-store is a stand-alone library and we can manage the local/component state with the help of @ngrx/component-store. It is similar to the reactive push-based “service with a subject” approach.
Key Concepts
- The local state must be initialized, but it has to be done lazily.
- The local state is bound to the life-cycle of that particular component and is cleaned up whenever that component is destroyed/done.
- We can update the state with the help of ComponentStore. We will have to use setState or updater, to change/update the state.
- Same as an update, we can also read the state with the help of ComponentStore’ select or a top-level state$.
- We can also start side-effects with ComponentStore’ effect for both sync and async. we can also feed the data both imperatively or reactively.
We will understand about that read, write, and effect later on this article.
Installation:
- If you are using npm to install the package, you can install it with the following command:
npm install @ngrx/component-store –save
- If you are using yarn use the following command:
yarn add @ngrx/component-store
- If you are going to add this to your project use the following command:
ng add @ngrx/component-store@latest
After this step some automated command will be fired, like:
- Your package.json file will be updated with dependencies->@ngrx/component-store.
- The package manager will be executed to install newly added dependencies.
Initialization:
There are two ways to initialize Component-Store:
- By using constructor
- By calling the setState method and passing an object that matches that particular state interface.
By using Constructor:
The state will be immediately available to the component store consumers whenever we use initialization with the constructor.
Matches.ts:
export interface MatchesState {
Matches: Match[];
}
@Injectable()
export class Matches extends ComponentStore<MatchesState> {
constructor() {
super({Matches: []});
}
}
Lazy initialization:
In these scenarios, if there is no meaning data in the component-store then developers do not need selectors to return any state. To avoid this, we have to initialize the state lazily with the help of setState and passing the full state to it. We can use the same approach to reset the state.
NOTE: We might face an error if we don’t complete the initialization before updating the state.
Match-page.component.ts
@Component({
template: `
<li *ngFor=”let match of (matches$ | async)”>
{{ match.name }}
</li>
`,
providers: [ComponentStore],
})
export class MatchesPageComponent {
readonly movies$ = this.componentStore.state$.pipe(
map(state => state.matches),
);
constructor(
private readonly componentStore: ComponentStore<{matches: Match[]}>
) {}
ngOnInit() {
this.componentStore.setState({matches: []});
}
}
Reading State:
The select method is the main part of the reading state. The select method will take a projector which will describe how the state is retrieved and/or transformed. The selector will emit new values, whenever values will change-the new value will be no longer distinct by comparison from the previous values.
Matches.ts
export interface MatchesState {
matches: Match[];
}
@Injectable()
export class MoviesStore extends ComponentStore<MatchesState> {
constructor() {
super({matches:[]});
}
readonly movies$: Observable<Match[]> = this.select(state => state.matches);
}
Match-page.component.ts
@Component({
template: `
<li *ngFor=”let match of (matches$ | async)”>
{{ match.name }}
</li>
`,
providers: [Matches],
})
export class MoviesPageComponent {
matches$ = this.Matches.matches$;
constructor(private readonly matches: Match) {}
}
Combining selectors:
We can also combine these selectors with other selectors or observables.
Matches.ts
export interface MatchesState {
matches: Match[];
userPreferredMatchesIds: string[];
}
@Injectable()
export class MatchesStore extends ComponentStore<MatchesState> {
constructor() {
super({matches:[], userPreferredMatchesIds:[]});
}
readonly Matches$ = this.select(state => state.matches);
readonly userPreferredMatchesIds$ = this.select(state => state.userPreferredMatchesIds);
readonly userPreferredMatches$ = this.select(
this.matches$,
this.userPreferredMatchesIds$,
(matches, ids) => matches.filter(match => ids.includes(matches.id))
);
}
Get method:
As we show, selector provides a reactive way to read the state from component-store with the help of observable, but sometimes it’s not necessary, what we need is imperative read. one of those cases is accessing the state within effects and that’s where we will have to use the get method.
Updating the State:
There are three ways to update the state with the help of component-store.
- By calling setState
- By calling patchState
- By creating an updater and passing value with it.
Updater method:
we can describe how the state changes with the help of the updater method. We take a pure function with the current state and values as arguments in updater and it will return a new state, updated immutably.
We can use more than one updater within a component-store. They are bonded with ‘case’ arguments or on () function in @ngrx/store reducer.
Matches.ts
@Injectable()
export class MatchesStore extends ComponentStore<MacthesState> {
constructor() {
super({matches: []});
}
readonly addMatch = this.updater((state, match: Match) => ({
matches: […state.matches, match],
}));
}
Now we can call an updater with the values imperatively or we can take an observable.
Match-page.comnponent.ts
@Component({
template: `
<button (click)=”add(‘New Match’)”>Add a Match</button>
`,
providers: [MatchesStore],
})
export class MatchesPageComponent {
constructor(private readonly matchesStore: MatchesStore) {}
add(match: string) {
this.matchesStore.addMatch(match);
}
}
setState method:
We can call the setState method by either providing the object of state type or as a callback.
The state will reset to its value when the object is provided. The lazy initialization performed the same way. We can also change the state partially with the help of the callback approach.
Match-page.component.ts
@Component({
template: `…`,
providers: [ComponentStore],
})
export class MatchesPageComponent implements OnInit {
constructor(
private readonly componentStore: ComponentStore<MatchesState>
) {}
ngOnInit() {
this.componentStore.setState({matches: []});
}
resetMatches() {
// resets the State to empty array
this.componentStore.setState({matches: []});
}
addMatch(match: Match) {
this.componentStore.setState((state) => {
return {
…state,
matches: […state.match, match],
};
});
}
}
patchState method
We can call the patchstate method by providing a partial state object or a partial updater callback. It will patch the state with the provided value whenever the partial state is provided and it will patch the state with the value returned from the callback whenever we provide the partial updater.
Match-page.component.ts
interface MatchesState {
matches: Match[];
selectedMatchId: string | null;
}
@Component({
template: `…`,
providers: [ComponentStore],
})
export class MatchesPageComponent implements OnInit {
constructor(
private readonly componentStore: ComponentStore<MatchesState>
) {}
ngOnInit() {
this.componentStore.setState({matches: [], selectedMatchId: null});
}
updateSelectedMatch(selectedMatchId: string) {
this.componentStore.patchState({selectedMatchId});
}
addMatch(match:Match) {
this.componentStore.patchState((state) => ({
matches: […state.matches, match]
}));
}
}
Effects:
We can extract any side-effects as network calls with the help of Effects.
Effect method
We have to pass a callback with an Observable of value into the effect method, which will describe how new incoming values should be handled. a value will be pushed into that observable at each new effect call.
@Injectable()
export class MatchesStore extends ComponentStore<MatchesState> {
constructor(private readonly matchesService: MatchesService) {
super({matches: []});
}
// Each new call of getMovie(id) pushed that id into movieId$ stream.
readonly getMatch = this.effect((matchId$: Observable<string>) => {
return matchId$.pipe(
//
switchMap((id) => this.matchesService.fetchMatch(id).pipe(
//
tap({
next: (match) => this.addMatch(match),
error: (e) => this.logError(e),
}),
//
catchError(() => EMPTY),
)),
);
});
readonly addMatch = this.updater((state, match: Match) => ({
matches: […state.matches, match],
}));
selectMatch(matchId: string) {
return this.select((state) => state.matches.find(m => m.id === matchId));
}
}
Now we can use getMatch effect within a component
Match.component.ts
@Component({
template: `…`,
// ❗️MatchesStore is provided higher up the component tree
})
export class MatchComponent {
Match$: Observable<Match>;
@Input()
set matchId(value: string) {
// calls effect with value.
this.matches.getMatch(value);
this.match$ = this.matchesStore.selectMatch(value);
}
constructor(private readonly matchesStore: MatchesStore) {}
}
Conclusion
Whenever we are working on a large-scale angular project, it’s better to start managing the state at the component level. A state is simply an object which represents how your interface will look at that particular moment, with the help of @ngrx/component-store and its three concepts: select, update, and effect we can handle it easily.
Author Bio: Ajay Patel – Technical Director, iFour Technolab Pvt. Ltd.
A Seasoned technocrat with years of experience building technical solutions for various industries using the latest technologies. With sharp understanding and technical acumen, have delivered hundreds of Web, Cloud, Desktop, and Mobile solutions and is heading the technical department at iFour Technolab Pvt. Ltd.
Hire Angular developers from iFour Technolab to build astonishing Front-ends of your business applications.
Image:
LinkedIn: https://www.linkedin.com/in/ajay-patel-8406167a