Communicate between Angular components via @NgRx

Posted on by

In the previous article, I explained how to communicate between Angular components using Observables. I also mentioned Redux. I indeed love the idea of a single immutable and, eventually, predictable state.

I would like to introduce a @ngrx/store library which is a Reactive implementation of Redux for Angular. It might be the overkill for small applications, although this is an easy way to create data streams across the app.

In this post, I will revise the notification app from the previous example and build a solution using @ngrx.

Preparation

First of all, we need to install a @ngrx/store library.


npm install @ngrx/core @ngrx/store --save

I assume that you already have the code from the previous article. If no, you can check it there.

What we need to do is to get rid of a NotificationService from anywhere.

Actions and Reducers

The workflow of making changes to a state is pretty simple.

First, you dispatch an action, which is an object with type and payload properties.

Then you run a reducer function. The reducer is a simple function, which receives the current state and action, and returns a new state based on action type.

Finally, all subscribers get notified by the new state.

Let’s start with creating our first reducer.

Import an Action interface and create a “notificationReducer” function. It has two parameters: current state and action. We also need to define an initial state as a fallback, in our case initial state of notification is equal to null.


// notification.ts
import { Action } from '@ngrx/store';

export interface Notification {
  type: String,
  message: String,
}

export const SET = 'SET';
export const RESET = 'RESET';

export const notificationReducer = (state: Notification = null, action: Action) =>
  action.type === SET ? {
    ...state,
    type: action.payload.type,
    message: action.payload.message
  } :
  action.type === RESET ? null :
  state;

We already know that reducer should return the new state based on action type. It’s a common pattern to use the switch for that purpose.

I prefer to either use the ternary operator or Ramda’s R.cond() function. With this approach, we can get rid of curly braces and return statements in our arrow functions.

We have two different action types for setting and resetting the notification. I will create constants for them.

We will return a copy of the current state with updated notification if the type is equal to SET.

We will return null if the type is equal to RESET.

And finally, we will return current state by default, if action type doesn’t match anything.

Note that in ES7 we can use a spread operator for objects. You can also use R.merge().

Store

Reducer is ready now. The next step is registering a store in our application.

To do so, open the module file. Import StoreModule from @ngrx/store, and provide a store with notification property with the notificationReducer.


// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { HttpModule } from '@angular/http';

import { AppComponent } from './app.component';
import { NotificationComponent } from './notification/notification.component';

import { StoreModule } from '@ngrx/store';
import { notificationReducer } from './notification/notification';

@NgModule({
  declarations: [
    AppComponent,
    FhNotificationComponent,
    FhNoPreviewDirective,
    FhFolderContainerComponent,
    FhFolderComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpModule,
    StoreModule.provideStore({ notification: notificationReducer }),
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }

Now we can retrieve a notification value from the state. In the AppComponent, import the Store from @ngrx/store and Observable from “rxjs”. Then define a notification variable as Observable with a type Notification, create an interface for AppState and inject a store with that type to the component.


// app.component.ts
import { Component } from '@angular/core';
import { Observable } from 'rxjs';
import { Store } from '@ngrx/store';
import { SET, Notification } from './notification/notification';

interface AppState {
  notification: Notification;
}

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
})

export class AppComponent {
  notification: Observable;

  constructor(private store: Store) {
    this.notification = this.store.select('notification');
  }
}

Now you can access any property in the store via the select method.

Action

Now it’s time to execute our reducer functions. It could be done via the store’s dispatch method.

Dispatch function receives an action as a parameter and then goes through all reducers. Many developers create actions separately but to keep it simple I will pass the Action object manually.

Let’s create two methods inside the AppComponent for error and success messages. Inside each method, we will dispatch an action with the type “SET” and new notification as a payload.


// app.components.ts
...
export class AppComponent {
  notification: Observable;

  constructor(private store: Store) {
    this.notification = this.store.select('notification');
  }
  
  success() {
    this.store.dispatch({
      type: SET,
      payload: {
        type: 'success',
        message: 'Success! Everything went as expected.'
      }});
  }
  error() {
    this.store.dispatch({
      type: SET,
      payload: {
        type: 'error',
        message: 'Error! Something went wrong'
      }});
  }
}

We also need to update the NotificationComponent’s close() method.

Import the Store first, and then add a dispatcher for RESET action. We don’t need to provide payload here as it’s an optional parameter.


// notification.component.ts
import { Component, Input } from '@angular/core';
import { Store } from '@ngrx/store';
import { RESET, Notification } from './fh-notification';

@Component({
  selector: 'notification',
  templateUrl: './notification.component.html',
})
export class NotificationComponent {
  constructor(private store: Store) {}

  close() {
    this.store.dispatch({ type: RESET });
  }

  @Input()
  notification: Notification;
}

Final Touch

Here is a simple implementation of the @ngrx/store to communicate between components in the Angular application.

But wait… Notification component doesn’t appear in the view.

It’s because notification is the Observable, and to make it work you need to use an async pipe.

Since we are passing it via @Input, don’t forget to add parentheses. So, it will send the value afterward.


<!-- app.component.html -->
<notification [notification]="(notification | async)"></notification>

<button class="Btn Btn--green text--14px text--bold u-marginRight-s" (click)="success();">success();</button>
<button class="Btn Btn--red text--14px text--bold" (click)="error();">error();</button>

I enjoy working with the @ngrx/store library. The idea is very simple and at the same time elegant. It’s easy to maintain the whole state in one place, and predictability is what I needed for complex Angular applications.

I suggest taking a look at Redux Dev Tools. With all the features such as time traversal, it makes debugging so much easier.

Hope you find this post helpful and happy coding!