Migrating from NGRX to NGXS in Angular 6
By: Josh Hicks
Tags: Angular, JavaScript, Ngxs, Ngrx, Migrate

Step 1: Installation

Before you can start refactoring you must install all the dependencies you need. I use NPM so that’s what I will show here but you can use Yarn if you like.

If you’re using Angular 6.x or higher run:

npm install @ngxs/store — save

This will get you the basics. However, you will probably want more depending on your specific needs. You will need the devtools plugin though. Run this to install:

npm install @ngxs/devtools-plugin — save-dev

If you need more plugins you can find them here.

Lastly, you will want to add the CLI schematics to help with generating the new files by running:

ng add @ngxs/schematics

Step 2: Importing the modules

This step will once again change depending on your specific needs but assuming you have global app-level state, you can import the default modules inside your AppModule file.

// app.module.ts import { BrowserModule } from '@angular/platform-browser'; // other imports here... (ngrx etc) import { NgModule } from '@angular/core';

import { NgxsModule } from '@ngxs/store';

import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';

import { YourState } from '../path/to/your-state';

import { AppComponent } from './app.component'; @NgModule({

declarations: [

AppComponent

],

imports: [

BrowserModule, // ... other imports here (NGRX etc.) NgxsModule.forRoot([

YourState

]),

NgxsReduxDevtoolsPluginModule.forRoot({

name: 'NGXS store',

disabled: environment.production

})

],

providers: [],

bootstrap: [AppComponent]

})

export class AppModule { }

Notice the Devtools plugin meta-data we added here. This will be used to differentiate the new NGXS store from the old NGRX store when using the inspector.

Step 3: Creating your State class

This step is where the largest changes will take place. In NGRX this code will be split into multiple different files. However, in NGXS you could potentially have it all in just one, though I don’t suggest it. So, let’s say you have the following files:

app.reducer.ts

app.effects.ts

app.actions.ts

Those files will become

app.state.ts

app.actions.ts

So right away we can see the boilerplate code melting away before our eyes! You could argue that you’re ignoring a separation of concerns by doing this but that’s all a matter of opinion. Let’s keep going.

Now let’s create the main files by running:

ng g @ngxs/schematics:store --name app

// app.state.ts import { State, Action, StateContext } from '@ngxs/store';

import { User } from './models/user';

import { Login } from './app.actions.ts'; export interface AppStateModel {

user: User | undefined;

} const initialState<AppStateModel> = { user: undefined }; @State<AppStateModel>({

name: 'app',

defaults: initialState

})

export class AppState {

@Action(Login)

login({patchState}: StateContext<AppStateModel>, action: Login){

patchState({user: action.user});

}

}

Step 4: Creating your Actions file

This part is actually pretty straight forward. The actions are almost exactly the same in each library except for a few slight differences.

// app.actions.ts (NGRX) import { Action } from '@ngrx/store'; export enum ActionTypes {

Login = '[Login Page] Login',

} export class Login implements Action {

readonly type = ActionTypes.Login;

constructor(public payload:{ username:string; password:string}){}

} export type Union = Login;

This file would become:

// app.actions.ts (NGXS) export class Login {

static readonly type = '[Login Page] Login';

constructor(public user: User){}

}

Much less code!

Now you just have to repeat that same process until all of your actions have been converted. Or until the feature you’re converting has been finished.

Step 5: Making improvements

This is a great start but we can do better. If you aren’t familiar with the concept of “facades” in Angular, this would be a great time to start learning. Essentially, it helps you keep your components powerless. They don’t get direct access to the store. They don’t get to mutate anything directly. It is a middle layer that handles all the business logic which makes it much easier to test as well. In this situation the largest value will come in the form of updating dependencies.

For example, if you have 100 components in your app that import the @ngrx/store to dispatch actions and select state you now have to update ALL of those import statements to use @ngxs/store . Which doesn’t sound that hard with the use of tools like “find and replace all” but it’s not that simple if you need to do this refactor incrementally over time. Keep in mind you will probably need to run both NGRX and NGXS at the same time. Meaning you will need to double your imports.

import { Store, select } from '@ngrx/store';

import { Store, Select } from '@ngxs/store;

If you look closely at these imports you’ll see that there is already an issue. We are duplicating imports with the same name. Store !== Store in this case. Meaning you will get a warning about duplicates. This is why I highly recommend abstracting this import into a singleton service that handles all the Store interactions for you, AKA a facade.

If we pull this into the facade service, app.service.ts we can just import it one time and then just inject the service into all the components!

// app.service.ts import { Injectable } from '@angular/core';

import { Select, Store } from '@ngxs/store';

import { Observable } from 'rxjs';

import { AppState } from './app.state'; @Injectable({

providedIn: 'root'

})

export class AppService { // TODO: Add selectors and action dispatchers }

This is MUCH easier than doing something like this.

// app.component.ts (don't do this) import { Store } from '@ngrx/store';

import { Store as ngxsStore } from '@ngxs/store';

This kind of aliasing gets very confusing, very fast. Not to mention you’re creating more tech debt from day one.

So if you plan on using both libraries and refactoring piece by piece, do your self a favor and use a facade.

Step 6: Replace Action handling in existing components

By now you must be asking yourself how the application will know which store to use. You have converted the Store but not the dispatching and selecting! This is how we will make the switch.

Right now, all your actions are still being dispatched by the NGRX store. In order to migrate it over we need to replace all the places where actions are getting dispatched with a wrapper function around the new facade implementation. For example:

// app.component.ts import { Store } from '@ngrx/store'; ...component stuff this.store.dispatch(

new Login(newUser)

);

will become…

// app.component.ts import { AppService } from './app.service'; ...component stuff login(newUser) {

this.appService.login(newUser);

}

Now the old NGRX store will have nothing to do with the actions that we have migrated over. Keep in mind there may still be some actions in your component that have not been converted yet, and that’s okay. Just do what is right for your current scope of migration.

Lastly, we can replace all the selectors. This is actually my favorite part of NGXS. I feel that it makes it very easy to select state from the store.

// app.component.ts (NGRX) ...component stuff public user: User; this.user = this.store.pipe(select(fromRoot.getUser));

could become…

// app.service.ts import { Store, Select } from '@ngxs/store';

import { AppState, User } from './app.state.ts'; @Select(AppState) user$: Observable<User>; // app.component.ts (NGXS) ...component stuff public user: User; ngOnInit() {

this.appService.user$.subscribe(user => this.user = user);

}

There are several different ways to do selectors. I encourage you to explore which way makes the most sense to you. However, the main thing to keep in mind is that you need to make sure you’re selecting from the NEW store and consuming it in the component. The easiest way to make sure of that is to use your facade.

Step 7: Inspect both stores in the Devtools

At this point we should be ready to see things in action. We can do this by opening Redux devtools.

Inside the app.module.ts file we told the Redux devtools which stores we would like to use. We even added a specific name for it name: 'NGXS Store' . We can now use that, along with our existing NGRX store, in the devtools inspector.

After opening the devtools you should see a drop-down at the top right. Inside the drop-down list you will see all the stores currently available. You should see both stores at this point. There are two main steps you need to take to verify everything is working as intended.

1: Verify the old store is no longer tied to those actions.

2: Verify that the new store is updating when actions are dispatched.

You can do this by running through the application and causing events that will trigger actions you converted. Then make sure that it only updates state in your NGXS store.

Summary

I have shown you the basics of converting your app from NGRX to NGXS. The scale of this will change drastically depending on the size of your application. However, you must keep in mind that this does not have to be done all at once. I recommend breaking things down feature by feature. This way you you can closely follow the structure of your state and keep track of your progress. Also, don’t forget to remove state in your NGRX store as they get moved over to NGXS.

If you have any questions or tips to improve this guide please let me know.

How do you eat an elephant? One bite at a time! 😃