Binge On Code

Jun 8, 2023

1337 readers

What is ngxs and how to use it in angular

Any modern web application needs a modern way of handling reactive programming challenges. In this guide, we will look at how ngxs can help you achieve this.

Well, now that you have your web application ready, you start to feel that there is a way that you could possibly handle reactive programming issues in a better way. Well, the good news is that there is something just for that, and that is ngxs.

But, what is ngxs, you may wonder. Simply put, it is a library which makes it possible for your application to have a single source of truth in a predictable manner.

So, the first part of this tutorial will give an introduction to what ngxs is and the second part will now show the moving parts needed to use it when it comes to angular state management.

Finally, we will look at how angular ngxs can be added onto your angular application and how to use it to update your application.

What is ngxs?

Well, as mentioned above, it is a library for angular state management. Okay, that is correct, but what is it? Well, to understand what is it, it is best to understand what makes it tick, as in, what are the moving parts.

There are three core parts that define ngxs:

  • State.
  • Model.
  • Actions.

Well, I know if you have worked with other state management libraries like ngrx, you must have wondered why we have no selectors and reducers. Well, simply put, ngxs has less boiler plate than ngrx does.

The moving parts of ngxs.

So, with the core parts in place, the next thing is understand what each of them actually are.

State

Yeah, you guessed it right, this is what defines the current standing of your web application. It is this which contains the most recent updates available to your application, and thus keeps the user interface in sync as well.

Another IMPORTANT distinguishing thing is that with unlike ngrx where we have reducers separately used to create the next state, with angular ngxs, the next state is actually created within the state itself!

Well, this may be a lot to take in, but not to worry, we will see an example at the bottom.

Model

This is simply a definition of how your store above would look like.

Actions

Well, it seems that both in ngxs and ngrx, actions are the constant feature. Just as you would have guessed it, actions decide what needs to be done immediately next in the application.

Of course there may be multiple actions happening at a time, for example you have Websockets in place. Now to distinguish these actions, each action is defined in a way unique to it as you will see in the example provided.

So now what is remaining is how to use ngxs.

We will use a hypothetical application which fetches artists from somewhere like spotify for example. Then our component will be updated purely by angular ngxs, so no more relying on rxjs observables and the likes.

Step 1

We will create a model file as well as an interfaces file. The interfaces will have definition of how our data attributes will look like. As for the model file, we will only have the model definition.

export interface IArtist {
  image: string;
  name: string;
  genres: Array<string>;
  albums: Array<string>;
  uuid: string;
}

Defining our angular ngxs model:

import { IArtist } from "./interfaces";

export interface ArtistsStateModel {
    searchResults: Array<IArtist>;
}

Step 2

We will create an actions file and add an action for fetching the artists.

export namespace ArtistsActions {
    export class SearchForArtist {
        static readonly type = '[Artists] Search For Artist';
        constructor (
            public searchTerm: string
        ) {}
    }
}

So, to explain the above code:

export namespace ArtistsActions

Here, we are namespacing this actions file, and wrapping all our actions, so that we just import the name of the namespace as a unit, instead of importing the individual action. This reduces margin for error and makes code look a little bit cleaner.

static readonly type

This line will define the name for our action. You can think of it of how you would want your action to be identified by the application.

Well, this name can be anything you want, but it is best to have it like so:

'[Component calling the action] Name of the action'

as this will make it easier to troubleshoot.

The other important part is the:

constructor (
    public searchTerm: string
) {}

Well, you guessed it right. This is the parameter to be passed into the action. In this case, we are passing in the searchTerm. But it is optional, if you don’t need this ngxs action to receive any parameters.

Step 3

Now, we define our angular ngxs state, like so:

import { Injectable } from "@angular/core";
import { Action, State, StateContext, StateToken } from "@ngxs/store";
import { ArtistsActions } from "./actions";
import { ArtistsStateModel } from "./model";
import { produce } from 'immer';
import { ApiService } from "../services/api.services";
import { tap } from "rxjs/operators";

const ARTIST_STATE_TOKEN = new StateToken<ArtistsStateModel>('artists');

@State({
    name: ARTIST_STATE_TOKEN,
    defaults: {
        searchResults: []
    }
})
@Injectable()
export class ArtistsState {
    constructor(
        private apiService: ApiService
    ) {}

    @Action(ArtistsActions.SearchForArtist)
    searchForArtist(ctx: StateContext<ArtistsStateModel>, action: ArtistsActions.SearchForArtist) {
        return this.apiService.get('search', {q: action.searchTerm, type: 'artist'}).pipe(
            tap(r => ctx.setState(produce(draft => {
                draft.searchResults = r;
            })))
        )
    }
}

Breaking this code line by line:

const ARTIST_STATE_TOKEN = new StateToken<ArtistsStateModel>('artists');

Here, we define how this piece of the whole application state will be identified in the whole angular state management. The type accepted by StateToken is the model definition for the state, in this case ArtistsStateModel, and the name is the string identity for the piece of state.

The next part is this:

@State({
    name: ARTIST_STATE_TOKEN,
    defaults: {
        searchResults: []
    }
})

What this does is simply set the name identified by our state token, as well as the initial state (passed in as defaults) which should be analogous to the model definition.

@Injectable()

Well, an angular ngxs state is a service of sorts. So that is why we define it using the @Injectable() decorator.

constructor(
    private apiService: ApiService
) {}

Here, we inject the services which we will be using, and in our case, since we are making an external request, we add our ApiService.

@Action(ArtistsActions.SearchForArtist)

Remember when we said actions say what needs to be done, well, here it is. This @Action decorator will accept a parameter of the action which it needs to listen for and act upon when such an action is dispatched.

searchForArtist(ctx: StateContext<ArtistsStateModel>, action: ArtistsActions.SearchForArtist) {
    return this.apiService.get('search', {q: action.searchTerm, type: 'artist'}).pipe(
        tap(r => ctx.setState(produce(draft => {
            draft.searchResults = r;
        })))
    )
}

So, the above method is the decorated class method, which accepts a context (the state model) and an action(the action which it has been decorated with).

Notice that we are tapping into it. This is because we need to invoke the actual fetching on the api.

action.searchTerm

Remember this code, we defined it in our action and now we will get it like so.

tap(r => ctx.setState(produce(draft => {
    draft.searchResults = r;
})))

This is actually the most important bit, because it is what makes it possible to now update the state with the new results.

We have produce method imported from immerjs (this is out of the scope of this tutorial). What it does is that it makes it possible to update the draft version of the current state, and return it, so that the draft’s updated attributes are then mapped onto the state attributes which match it, thus update the state. The reason for this is because it is not advisable to update the ngxs state directly.

So, you can see that what tap gives us, we now set this to the attribute of our state that we want to update.

Step 4

Now, let’s define our component:

import { Component } from '@angular/core';
import { IArtist } from "./interfaces";
import { ArtistsActions } from './actions';

@Component({
  moduleId: 'module.id',
  selector: 'lib-artists',
  templateUrl: 'artists.component.html',
  styleUrls: [
    'artists.component.scss'
  ]
})
export class ArtistsComponent {
  private searchResults$: Observable<Array<ISearchItem>> = new Observable<Array<ISearchItem>>();

  /**
   * 
   */
   public getSearchResults$(): Observable<Array<ISearchItem>> {
    return this.searchResults$;
  }

  constructor(
    private store: Store
  ) {
    this.searchResults$ = this.store.select(state => state.artists.searchResults || of([]))
  }

  /**
   * 
   * @param searchTerm 
   */
  public emitSearchEvent(searchTerm: string): void {
    this.store.dispatch(new ArtistsActions.SearchForArtist(searchTerm));
  }
}

Most of the code above is simple angular, so we will focus on the parts related to angular state management.

private searchResults$: Observable<Array<ISearchItem>> = new Observable<Array<ISearchItem>>();

This part defines our observable of search results. The results will be a list of IArtists type.

/**
* 
*/
public getSearchResults$(): Observable<Array<ISearchItem>> {
    return this.searchResults$;
}

This is a getter method which we will use in our html.

constructor(
    private store: Store
) {
    this.searchResults$ = this.store.select(state => state.artists.searchResults || of([]));
}

We need to define the store so that we can use it’s methods. The first method is the .select method, and this will return an observable of the most recent search results. Notice that because the search results may be empty, we are using rxjs of operator to return an initial observable of empty array.

/**
 * 
 * @param searchTerm 
 */
public emitSearchEvent(searchTerm: string): void {
  this.store.dispatch(new ArtistsActions.SearchForArtist(searchTerm));
}

This method will listen for when a user searches and then it will dispatch the action.

Step 5

Finally, in our search component html file, we will add this:

<div class="artist" *ngFor="let artist of (getSearchResults$()|async) || []"></div>

So, here, we are using an async pipe to read the latest emitted value from our getSearchResults$() method.

Conclusion

As you can see, it is this easy to use ngxs for your angular state management issues.

Well, that is it for now.

Happy Coding!

Related Articles

What is ngrx and how does it work?

A modern web application needs a modern solution to solve reactive programming issues. That is basically what ngrx is used for, as introduced in this article.

Jun 8, 2023

Views 145

Angular JavaScript React CSS Django Python React Native Next JS State Management Django Rest Framework Unity