How I met Angular + The Reactive State (NGRX)

There I was 3 years ago, searching through the web for the next big thing… and I found it. Some weird and strange ‘new’ way of writing JavaScript code: Typescript.

 

However, typescript didn’t come alone. It was embedded in the Angular framework. That’s how I actually met this new technology. It took several nights to understand how to build a single page application (SPA) with this, but all I can say is it was worth it. 

 

Although it was a new technology (and we as developers know how hard it can be to learn a new way of building web sites), I remember the feeling of completeness of learning something new and innovative.

 

A few months later, I learned (in a bad way) that Angular isn’t enough for managing your application state. Don’t get me wrong. Although it gives a very good structure for splitting your application into smaller modules and components, there is a need for managing the state of your app among all these modules. Especially if your project increases in many modules and you’d like to have a centralized location for managing the client-end side data.

 

With that in mind, I’d like to take you on a trip in order to build a small but powerful application in this tutorial. It will consist of a shopping cart. I won’t deepen in every step and component, but don’t worry, I will attach the link to the source code of the entire app at the end of this document. Basically, we will have 2 pages:

 

Articles.

This page will show you all the articles available in the system. For every article, the system will display a small image of it, a description, and an icon for adding it to the cart. For every article you add to the cart, the system will prompt a notification telling you that you added it successfully.

 

Shopping cart.

This page will display all the articles added to the cart. If you haven’t added anything to the cart, then you won’t be able to enter this page. Additionally, an icon at the top will count how many articles have been added to the cart. 

Here is a small glance of the app:

https://hiking-shop-front.netlify.com/home

 

Notes: 

Here is an article where it exposes how to deploy your angular-cli app to netlify (for free!)

The version of Angular that we will use is 8.2.9 (it is the latest one as I’m writing this article); For the CLI it is 8.3.8

 

What are the components?

Everything in Angular is a component. Well, at least everything we want to see on the page.
For our application, we will have the following components:

At the top, we will have our header component that will host the links for navigating through our app. In the middle, we will have two components that depending on the page we’re currently on, would be the component to be rendered by the Angular engine. 

Without further comments, let’s get into it. 

 

Pre – requisites.

Have installed the angular-cli on your computer. If you haven’t, then go to https://cli.angular.io/ and install it.
What are you waiting for?!

Create an Angular app by running the following command in your terminal: ng new hiking-shop-front -S –routing

hiking-shop-front: the name of the application to create by the angular-cli (feel free to change it if you like).

-S: When true, does not generate “spec.ts” test files for the new project.

–routing: When true, generates a routing module for the initial project.

 

You can see more options at https://angular.io/cli/new

Add the Angular Material styles to the project.

Just run: ng add @angular/material inside the folder of the project. For more info, take a look at https://material.angular.io/guide/getting-started

This will materialize our application! 😉

Add NGRX to the project. For this, 3 packages should be installed:

@ngrx/store. Follow the instructions from https://ngrx.io/guide/store/install

This package will handle the state of your application. It provides a mechanism for saving objects with the structure you want in order to receive and send information across your angular components.

@ngrx/store-devtools. Follow the instructions from https://ngrx.io/guide/store-devtools/install

This package will let you debug your store. No need for printing in the console every step of our application. Let’s use the tools like a pro. 

 

Additionally, you’ll need to install the redux chrome extension, where you’ll see the dispatched actions in the developer tools: https://chrome.google.com/webstore/detail/redux-devtools/lmhkpmbekcpmknklioeibfkpmmfibljd?hl=es

@ngrx/effects. Follow the instructions from https://ngrx.io/guide/effects/install

This package allows you to interact with any service that handles your requests to your API. Its goal is to treat the data before and after you make a request, and then call your store for saving the data.

 

Building our app.

The first thing to do is to create a module for the angular material. For that, go to your application folder in your terminal, and type:

ng generate module angular-material

With this, you’ll get: 

import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';

@NgModule({
    declarations: [],
    imports: [
        CommonModule
    ],
    exports: []
})
export class AngularMaterialModule {
}

In this module, we will add all the elements from Angular Material that we would like to use in our app. One final thing is to add this module into our app module. So add it in the import array.

Let’s create our set up for the ngrx now. This architecture needs three things mainly.

 

Actions.

The only way of changing the state of your app is by firing an action. For instance, let’s say you’re saving how many times the user clicks on a specific button. You could have a global counter for this, and the only way of adding one to this counter is by firing the event: ‘Add count’. (Which essentially is a method that gets called every time we clicked on the button).

Here are the appropriate actions for our app:

import { createAction, props } from '@ngrx/store';

import { HikingArticle } from '../models/hiking-article.model';


export const GET_ITEMS_REQUEST = '[Shopping Cart Component] GET_ITEMS_REQUEST';

... <MORE ACTIONS> ...

export const selectOrDeselectItemForShopping = createAction(SELECT_OR_DESELECT_ITEM_FOR_SHOPPING, props<{ articleID: string, isSelected: boolean }>());

 

Reducer.

Here is where the state of your app is saved. Basically, we create an object with the attributes we want to save and update, for every action we fire in ANY component of the application.

Here is the reducer of our app: 

import { createReducer, on } from '@ngrx/store';
import * as appActions from './app.actions';
import { HikingArticle } from '../models/hiking-article.model';

export interface HikingState {
    hikingArticles: HikingArticle[];
    itemsToBuy: HikingArticle[];
}

export const initialState: HikingState = {
    hikingArticles: [],
    itemsToBuy: []
};

const hikingReducer = createReducer(initialState,
    on(appActions.getItemsSuccess, (state: HikingState, { items }) => {
        return {
            ...state,
            hikingArticles: [ ...items ]
        };
    }),
    ...<MORE ACTIONS>...
);

export function HikingReducer(state: HikingState, action) {
    return hikingReducer(state, action);
}

 

Effects.

When you want to treat data before and after you make a request to your API, here is the place for that. The effect gets called by firing an action, so you would have to create actions for your reducer and also for your effects. Once you have treated the data as you want, you can fire another action for calling the reducer.

 

Note:

Something very important with this approach is that you can listen to the same action in the reducer and in the effect. It is not a very common approach (and also it may not be a correct one), however, if you find a situation where this is needed, take into consideration that it will be called the reducer first and then the effect.

Here is the effects file of our app.

import { Injectable } from '@angular/core';
import { Actions, createEffect, ofType } from '@ngrx/effects';

import * as actions from '../reducers/app.actions';
import { HikingArticle } from '../models/hiking-article.model';
import { map } from 'rxjs/operators';

@Injectable()
export class AppEffects {

   ... <PROPERTIES> ...

    loadArticles$ = createEffect(() =>
        this.actions$.pipe(
            ofType(actions.GET_ITEMS_REQUEST),
            map(() => {
                // let's sort the array randomly, so all items will appear in different positions:
                const itemsSorted = this.articlesFromBackend.sort(() => 0.5 - Math.random());
                return actions.getItemsSuccess({ items: itemsSorted });
            })
        )
    );

    constructor(private actions$: Actions) {
    }
}

Index.ts file

A good practice for exposing your reducer is to create an index file. You can have it at the same level as your actions, effects, and reducer. Here is an example of what you should expose: 

import { ActionReducerMap } from '@ngrx/store';
import { HikingReducer, HikingState } from './app.reducers';

export interface AppState {
    hiking: HikingState
}
export const appReducers: ActionReducerMap<AppState> = {
    hiking: HikingReducer
};
import { ActionReducerMap } from '@ngrx/store';

import { HikingReducer, HikingState } from './app.reducers';

export interface AppState {
    hiking: HikingState
}

export const appReducers: ActionReducerMap<AppState> = {
    hiking: HikingReducer
};

Finally, at this point this is how your app.module.ts should look like:

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
import { FormsModule } from '@angular/forms';
import { StoreModule } from '@ngrx/store';
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
import { EffectsModule } from '@ngrx/effects';

import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AngularMaterialModule } from './shared/modules/angular-material.module';
import { environment } from '../environments/environment';
import { AppEffects } from './reducers/app.effects';
import { appReducers } from './reducers';

@NgModule({
    declarations: [
        AppComponent,
    ],
    imports: [
        BrowserModule,
        AppRoutingModule,
        BrowserAnimationsModule,
        AngularMaterialModule,
        FormsModule,
        StoreModule.forRoot(appReducers, {
            runtimeChecks: {
                strictStateImmutability: true,
                strictActionImmutability: true
            }
        }),
        StoreDevtoolsModule.instrument({ maxAge: 25, logOnly: environment.production }),
        EffectsModule.forRoot([ AppEffects ])
    ],
    providers: [],
    bootstrap: [ AppComponent ]
})
export class AppModule {
}

Check StoreModule, StoreDevtoolsModule, and EffectsModule from the import array. Those attributes will do the magic for the NGRX in your application. 

 

The Header Component.

In here, we will put all the links for our application, so the user would be able to navigate through the pages of our app. Additionally, let’s add an icon for the shopping cart that will display the number of articles added to it. With this very basic need, we will see the power of our state in action. 

The .ts file of our component looks like this:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { HikingArticle } from '../models/hiking-article.model';
import { select, Store } from '@ngrx/store';
import { AppState } from '../reducers';

@Component({
    selector: 'app-header',
    templateUrl: './header.component.html',
    styleUrls: ['./header.component.scss']
})
export class HeaderComponent implements OnInit {
    items$: Observable<HikingArticle[]>;

    constructor(private store$: Store<AppState>) {
    }

    ngOnInit() {
        this.items$ = this.store$.pipe(
            select(state => state.hiking.itemsToBuy)
        );
    }
}

The items$ attribute will host the objects added to the cart. Let’s take a look at our HTML that will show the links and also, how the objects from the cart are being used in order to display the number of items added to the shopping cart:

<mat-toolbar color="primary">
  <span>
    <a mat-button
       matTooltip="Home"
       routerLink="/">
        <mat-icon aria-hidden="false" aria-label="Home">home</mat-icon>
    </a>
  </span>
    <span class="spacer"></span>
    <ul>
        <li>
            <a mat-button
               matTooltip="Articles"
               matTooltipPosition="before"
               routerLink="/articles">
                <mat-icon aria-hidden="false"aria-label="Articles">loyalty</mat-icon>
            </a>
        </li>
        <li>
            <a mat-button
               *ngIf="(items$ | async) as items"
               matTooltip="Shopping Cart"
               [ngClass]="{'item-disabled':items.length < 1 }"
               routerLink="/cart">
                <mat-icon aria-hidden="false" aria-label="Shopping Cart"
                          [matBadge]="items.length.toString()"
                          matBadgeOverlap="false"
                          matBadgeColor="warn">
                    shopping_cart
                </mat-icon>
            </a>
        </li>
    </ul>
</mat-toolbar>

Pretty cool, uh? With this simple check every time some article gets added to the state, it will automatically update the count of the articles and will show the correct count to the user. NGRX uses the observer pattern for letting know all its subscribers when something has changed and updates it accordingly to the views.

 

Articles component.

In order to list the preview of the article, I have created a small component that is reused in the shopping list and article components. I called it the ‘article’ component (singular).

You may wonder why I did this, shouldn’t I be adding unnecessary complexity to the code?

The short answer is no because while I’m adding a component inside another component (and this could sound very complex), the purpose of angular is to create elements that avoid having duplicated codes. 

 

In the previous image, every image container (with its description) is the article component. This allowed me to focus only on how I wanted to look at every preview; where the image should appear along with its title. Once I had the look of ONE article, I was able to replicate it.

 

Here is a look at the article.component.html:

<mat-card class="example-card"
          [ngClass]="{'item-disabled': item?.isAddedToTheCart}">
    <mat-card-header>
        <div mat-card-avatar class="example-header-image"></div>
        <mat-card-title>{{item?.name}}</mat-card-title>
        <mat-card-subtitle>{{item?.category}}</mat-card-subtitle>
    </mat-card-header>
    <img mat-card-image
         class="img-tag"
         src="{{item?.photoURI}}"
         alt="Photo of hiking item">
    <mat-card-content>
        <p>{{item?.description}}</p>
    </mat-card-content>
    <mat-card-actions *ngIf="!isInCart; else cartOptions">
        <button mat-button
                matTooltipPosition="right"
                matTooltip="Add to cart"
                (click)="addItemToShoppingList()">
            <mat-icon aria-hidden="false" aria-label="Articles">
                add_shopping_cart
            </mat-icon>
        </button>

    </mat-card-actions>
    <ng-template #cartOptions>
        <mat-card-actions>
            <button mat-button
                    matTooltipPosition="right"
                    matTooltip="Remove from cart"
                    (click)="removeItemFromShoppingList()">
                <mat-icon aria-hidden="false" aria-label="Articles">
                    remove_shopping_cart
                </mat-icon>
            </button>
        </mat-card-actions>
    </ng-template>
</mat-card>

Additionally, here is the .ts file: 

import { Component, Input } from '@angular/core';
import { HikingArticle } from '../../models/hiking-article.model';
import { Store } from '@ngrx/store';
import { AppState } from '../../reducers';
import {
    addItemToShoppingCart,
    removeItemFromShoppingCart,
    selectOrDeselectItemForShopping
} from '../../reducers/app.actions';
import { MatSnackBar } from '@angular/material/snack-bar';

@Component({
    selector: 'app-article',
    templateUrl: './article.component.html',
    styleUrls: [ './article.component.scss' ]
})
export class ArticleComponent {
    @Input()
    item: HikingArticle;
    @Input()
    isInCart = false;

    constructor(private store$: Store<AppState>,
                private _snackBar: MatSnackBar) {
    }

    addItemToShoppingList() {
        this.store$.dispatch(addItemToShoppingCart({ item: this.item }));
        this.store$.dispatch(selectOrDeselectItemForShopping({ articleID: this.item.id, isSelected: true }));
        this._snackBar.open('Item added to the cart', '', {
            duration: 2000
        });
    }

    removeItemFromShoppingList() {
        this.store$.dispatch(removeItemFromShoppingCart({ item: this.item }));
        this.store$.dispatch(selectOrDeselectItemForShopping({ articleID: this.item.id, isSelected: false }));
        this._snackBar.open('Item removed from the cart', '', {
            duration: 2000
        });
    }
}

 

The cool thing to notice from the ts file is how the store$ is being used and the actions you’d be able to dispatch from any point of the app, in order to change something in the reducer or when calling the effects for retrieving something from an API if needed. 

 

Finally, I created two more components: articles.component.ts and shopping-cart.component.ts where I simply call multiple times the article component (with the according arguments of every product), in order to render them with their specifications. 

 

In conclusion, I personally think that Angular + NgRX is a great tool for building SPAs as a robust framework. Certainly, there are other great tools out there that are able to create similar SPAs, so at the end it is up to you which one you would choose. I just wanted to show you what Angular can do. 

 

I hope this article can help you when deciding which technology you will use. 
Repository to the source code of the app: https://github.com/EddieBenji/hiking-shop

 

References.

Angular. (n.d.). Retrieved November 1, 2019, from https://angular.io/cli/new

Angular CLI. (n.d.). Retrieved November 1, 2019, from https://cli.angular.io/

Angular Material. (n.d.). Retrieved November 1, 2019, from https://material.angular.io/guide/getting-started

Deploying an Angular App to Netlify. (n.d.). Retrieved November 1, 2019, from https://scotch.io/tutorials/deploying-an-angular-app-to-netlify

NgRx Docs. (n.d.). Retrieved November 1, 2019, from https://ngrx.io/

Eduardo Canché

Author: Eduardo Canché