Create your account

Already have an account? Login here

Note: By joining, you will receive periodic emails from Coursetro. You can unsubscribe from these emails.

Create account

Angular NGXS Tutorial - An Alternative to Ngrx for State Management

By Gary simon - Apr 10, 2018

Last week, I created a tutorial that showed users how to use Ngrx for state management in Angular.

This week, I'm going to take the same exact sample project and walk you through the process of doing the same thing, except with NGXS.

Ngxs is a different approach to Angular state management that offers a few benefits, namely less boilerplate code.

Let's get started!

If you prefer watching a video..

Be sure to Subscribe to the Official Coursetro Youtube Channel for more videos.

Getting Started

As always, we're going to use the Angular CLI to start a new project in the console:

> ng new ngxs-intro
> cd ngxs-intro

Next, we'll install ngxs store, logger-plugin and devtools-plugin:

> yarn add @ngxs/store
> yarn add @ngxs/logger-plugin @ngxs/devtools-plugin --dev

I'm using yarn here, but you can use npm as well. The logger-plugin and devtools-plugin are there to help you when you're developing with NGXS. The logger plugin outputs the state in the console, and the devtools-plugin allows NGXS to work with Redux Devtools chrome extension.

While we're here in the console, we're going to use the Angular CLI to generate two components:

> ng g c read
> ng g c create

We'll use the read component to read from ngxs store and our create component to add data to the store.

Defining a Model

Create the folder and file: /src/app/models/tutorial.model.ts with the contents:

export interface Tutorial {
    name: string;
    url: string;
}

Our tiny little app will allow users to save a name and url of a tutorial. Awesome, huh?

Defining Actions

Similar to Ngrx, we define actions in NGXS as well. We'll create two actions for adding and removing tutorials.

Create the folder and file: /src/app/actions/tutorial.actions.ts with the contents:

import { Tutorial } from './../models/tutorial.model'

export class AddTutorial {
    static readonly type = '[TUTORIAL] Add'

    constructor(public payload: Tutorial) {}
}

export class RemoveTutorial {
    static readonly type = '[TUTORIAL] Remove'

    constructor(public payload: string) {}
}

The main difference here from Ngrx to Ngxs, is that in this example, the readonly type is static.

If our actions require data -- payload - we can pass it in through a constructor.

Defining a State

A key difference between Ngrx and Ngxs is how the state is handled. The state file(s) in Ngxs take the place of reducers in Ngrx. This is done by utilizing various decorators.

Create the folder and file: /src/app/state/tutorial.state.ts with the content:

// Section 1
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { Tutorial } from './../models/tutorial.model'
import { AddTutorial, RemoveTutorial } from './../actions/tutorial.actions'

// Section 2
export class TutorialStateModel {
    tutorials: Tutorial[];
}

// Section 3
@State<TutorialStateModel>({
    name: 'tutorials',
    defaults: {
        tutorials: []
    }
})
  • Section 1 
    These are our imports, which include importing various functions from ngrx store. We'll use these shortly.

  • Section 2 
    We create a state model. This could include additional properties based on your needs.

  • Section 3 
    We use the state decorator to define a name and default values based on the state model.

Add the following contents beneath the 3 sections above:

// Sections 1, 2 and 3 above removed for brevity

export class TutorialState {

    // Section 4
    @Selector()
    static getTutorials(state: TutorialStateModel) {
        return state.tutorials
    }

    // Section 5
    @Action(AddTutorial)
    add({getState, patchState }: StateContext<TutorialStateModel>, { payload }:AddTutorial) {
        const state = getState();
        patchState({
            tutorials: [...state.tutorials, payload]
        })
    }

    @Action(RemoveTutorial)
    remove({getState, patchState }: StateContext<TutorialStateModel>, { payload }:RemoveTutorial) {
        patchState({
            tutorials: getState().tutorials.filter(a => a.name != payload)
        })
    }

}
  • Section 4 
    The @Selector() decorator allows you to create functions to return data, or return specific results from your data. These allow you to reduce code in your components and access them from multiple locations. In our example, we're simply returning all of the tutorials, though, you could run a filter on them to return only specific results.

  • Section 5 
    We use the @Action() decorator to handle dispatched actions. We've defined 2 actions for adding a tutorial and removing one. Both actions include a payload, as both require data in order to handle the action appropriately, but payloads are not required.

    You can use getState() to get the current state, setState() and patchState(). We're using patchState() instead of setState() as it helps reduce the necessary code.

Updating App.Module

Now with our NGXS code ready to go, we need to add it to our App's module file. 

Open /src/app/app.module.ts and add the following:

// Other imports removed for brevity

import { NgxsModule } from '@ngxs/store';
import { TutorialState } from './state/tutorial.state';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';

@NgModule({
  ...
  imports: [
    BrowserModule,
    NgxsModule.forRoot([
      TutorialState
    ]),
    NgxsReduxDevtoolsPluginModule.forRoot(),
    NgxsLoggerPluginModule.forRoot()
  ]
  ...
})
export class AppModule { }

Easy!

Adding Tutorials to the State

Open up /src/app/create/create.component.ts and add the following:

import { Component, OnInit } from '@angular/core';
import { Store } from '@ngxs/store';
import { AddTutorial } from './../actions/tutorial.actions'

@Component({
  // props removed for brevity
})
export class CreateComponent implements OnInit {

  constructor(private store: Store) { }

  addTutorial(name, url) {
    this.store.dispatch(new AddTutorial({name: name, url: url}))
  }

  ngOnInit() {
  }

}

Similar to Ngrx, we use .dispatch() to dispatch an action. In our case, we're importing AddTutorial from our actions and dispatching it with a payload of name and url, which will come from a form.

Next, open create.component.ts and add:

<div class="left">

  <input type="text" placeholder="name" #name>
  <input type="text" placeholder="url" #url>

  <button (click)="addTutorial(name.value,url.value)">Add a Tutorial</button>
</div>

Update our /src/app/app.component.html file:

<app-create></app-create>
<app-read></app-read>

Also, add some CSS is /src/styles.css:

body, html {
    margin: 0;
    padding: 0;
    font-family: 'Arial';
}

.left, .right {
    float:left;
    width: calc(50% - 6em);
    padding: 3em;
}

input[type="text"] {
    width: 100%;
    padding: 5px;
    margin-bottom: 10px;
}

At this point, if you save and serve the project with ng serve -o in the console, you can add tutorials with the forum and view the logger in the console of the browser. It will show that the state is being updated.

Reading from the State

We've written to the state, let's read from it now.

Open up /src/app/read/read.component.ts and add the following:

import { Component, OnInit } from '@angular/core';
import { Store, Select } from '@ngxs/store';
import { Tutorial } from './../models/tutorial.model'
import { TutorialState } from './../state/tutorial.state' // We will use this shortly
import { Observable } from 'rxjs/Observable'
import { RemoveTutorial } from './../actions/tutorial.actions'

@Component({}) // Removed for brevity

export class ReadComponent implements OnInit {

  tutorials$: Observable<Tutorial>

  constructor(private store: Store) {
      this.tutorials$ = this.store.select(state => state.tutorials.tutorials)
  }

  delTutorial(name) {
    this.store.dispatch(new RemoveTutorial(name))
  }

  ngOnInit() {}

}

Here, we're using an instance of Store to select the tutorials from the state and binding it to an observable. 

We're also creating a method to handle dispatching the RemoveTutorial action.

Visit the create.component.html template and specify the following:

<div class="right" *ngIf="tutorials$">

  <h3>Tutorials</h3>
  <ul>
    <li (click)="delTutorial(tutorial.name)" *ngFor="let tutorial of tutorials$ | async">
      <a [href]="tutorial.url" target="_blank">{{ tutorial.name }}</a>
    </li>
  </ul>

</div>

If you save the project and add a tutorial, the tutorials will now show up in the read component's unordered list.  If you try clicking to the right of each tutorial, it will remove the tutorial from the state.

If you visit the read.component.ts file again and make these adjustments, you will find that we can make use of our state's Selector function for returning the state in even less code:

  //tutorials$: Observable<Tutorial>
  @Select(TutorialState.getTutorials) tutorials$: Observable<Tutorial>

  constructor(private store: Store) {
      //this.tutorials$ = this.store.select(state => state.tutorials.tutorials)
  }

This allows us to use the @Select decorator to pass in the getTutorials selector that we created in the state!

Conclusion

We just scratched the surface of NGXS here, but hopefully you now have a basic understanding of how to use this state management solution for Angular. 


Share this post




Say something about this awesome post!