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.
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
We will understand about that read, write, and effect later on this article.
Installation:
npm install @ngrx/component-store –save
yarn add @ngrx/component-store
ng add @ngrx/component-store@latest
After this step some automated command will be fired, like:
Initialization:
There are two ways to initialize Component-Store:
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: []});
}
}
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: []});
}
}
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) {}
}
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))
);
}
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.
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);
}
}
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],
};
});
}
}
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.
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) {}
}
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
Building a robust online presence is no longer optional for entrepreneurs; it’s a necessity. Websites… Read More
Advanced application security testing tools are key to the rapid pace of digital transformation. Applications… Read More
Endpoint management is a superhero today. It caters to various requirements of an organization. These… Read More
Today, it is impossible to conduct business by ignoring the online presence; therefore, it is… Read More
Simply put, supply chains are the cornerstone of modern businesses. They effectively connect organizations not… Read More
AI is transforming customer service by making it quicker and more intelligent. Chatbots and AI… Read More