Introduction to the NGRX Suite, Part 1
Tags: angular, typescript, ngrx, javascript
Published: 2021-04-09 17:58:35

Jim Armstrong | ng-conf | Oct 2020

![alt text](https://miro.medium.com/max/700/0*lePZfqGZ-sNKqn0C.png)
###### NgRx state management, courtesy [https://ngrx.io/guide/store](https://ngrx.io/guide/store)

An organized introduction to _@ ngrx/store, @ ngrx/effects,_ and _@ ngrx/entity_

**Introduction**

This article is intended for relatively new Angular developers who are just starting to work with an organized store in their applications. The [NgRx suite](https://ngrx.io/docs) is one of the most popular frameworks for building reactive Angular applications. The toolset does, however, come with a learning curve, especially for those not previously familiar with concepts such as [Redux](https://redux.js.org/tutorials/fundamentals/part-4-store).

In talking with new Angular developers, a common communication is frustration with moving from online tutorials such as counters and TODO apps to actual applications. This article attempts to bridge that gap with an organized and phased introduction to _@ ngrx/store, @ ngrx/effects,_ and _@ ngrx/entity_.

Instead of discussing all three tools in one massive tutorial, the application in this series is broken into four parts. This application is an extension of a quaternion calculator that I have frequently used as a ‘Hello World’ project for testing languages and frameworks. This calculator has been extended to more closely resemble a practical application that might be developed for an EdTech client.

Now, if the term _quaternions_ sounds mathematical and scary, don’t worry. If you have read any of my past articles, then you know we have a tried and true technique for dealing with pesky math formulas. Here it goes …

_blah, blah … math … blah, blah … quaternions … blah, blah … API._

Ah, there. We’re done :). Any math pertaining to quaternions is performed by my Typescript Math Toolkit _Quaternion_ class. The Typescript Math Toolkit is a private library developed for my clients, but many parts of it have been open-sourced.

All you need in order to understand this tutorial series is:

1 — Some prior exposure to _@ ngrx/store;_ at least a counter or TODO app (see the docs at [https://ngrx.io/docs](https://ngrx.io/docs), for example).

2 — Ability to work with a data structure containing four numbers.

3 — Ability to call an API for add, subtract, multiply, and divide.

4 — Exposure to basic Angular concepts and routing, including feature modules and lazy-loading.

```
<aside>
  While quaternions were conceived as an extension to complex numbers, 
they have several practical applications, most notably in the area of 
navigation. A quaternion may be interpreted as a vector in three-dimensional 
(Euclidean) space along with a rotation about that vector.  

  This use of quaternions was first applied to resolution of the so-called 
Euler-angle singularity; a situation where the formula for motion of an 
object exhibits a singularity at a vertical angle of attack. This situation 
is sometimes called gimbal lock. Equations of motion developed using 
quaternions exhibit no such issues. In reality, the Euler-angle equations 
are NOT singular; they are indeterminate. Both the numerator and denominator 
approach zero at a vertical angle of attack. L'Hopital's rule is necessary 
to evaluate the equations at this input value. Such an approach is 
cumbersome, however, and quaternions provide a cleaner and more efficient 
solution. 
 
  Quaternions are also used in inverse kinematics (IK) to model the motion 
of bone chains. Quaternions avoid 'breaking' or 'popping' that was prevalent 
in early 3D software packages that resolved IK motion using Euler-angle 
models.
</aside>
```

**The Application**

The application covered in this series is an abbreviated learning module involving quaternions and quaternion arithmetic. It consists of a login screen, a calculator that allows students to practice quaternion-arithmetic formulas, and an assessment test. An actual application might also include reading material on the topic, but that has been omitted for brevity. The general application flow is

1 — Login.

2 — Present student with the calculator for practice and option to take assessment test. The calculator is always displayed while the assessment test is optional.

3 — A test is scored after completion, and then results are displayed to student followed by sending the scored test to a server.

The tutorial series is divided into four parts, which might correspond to application sprints in practice:

Part I: Construct the global store by features using _@ ngrx/store_ and implement the calculator. Login and test views are placeholders.

Part II: Complete the test view using _@ ngrx/effects_ for retrieval of the assessment test and communication of scored results back to a server. Service calls are simulated using a mock back end.

Part III: Use _@ ngrx/entity_ to model and work with test data in the application.

Part IV: Implement the login screen using simple authentication and illustrate concepts such as redirect url. This further introduces how to use _@ ngrx/store_ in an environment similar to that you might encounter in actual work.

At present, stakeholders have prescribed that the student will always log in before being directed to the calculator practice view. As seasoned developers, we know that will change, so our plan is to work on the calculator first as it is the most complex view. The calculator also addresses the most complex slice of the global store.

Before continuing, you may wish to follow along or fork the Github for the application (in its Part I state).

[TheAlgorithmist/intro-ngrx](https://github.com/theAlgorithmist/intro-ngrx) on GitHub

**Models**

Before we can construct a global store, it is necessary to understand models required by each feature in the application. Following is an outline of each feature’s data requirements as initially presented. Only the calculator requirement is believed to be solid as of this article.

**User Model:** first name, last name, class id, student id, and whether or not the student is authenticated to use this application.

**Calculator Model:** Quaternion and calculator models.

**Test Model:** Test id, string question, quaternion values for the correct answer and the student’s input.

The application also has a requirement that once a test has begun, the student may not interact with the calculator.

**User model**

The working _User_ model at this point is

```
export interface User
{
  first: string;

  last: string;

  classID: string;

  studentID: string;

  authorized: boolean;
}
```

There is also ‘talk’ about possibly echoing the user’s name back to them on a successful answer, i.e. ‘That’s correct. Great job, Sandeep!’ For present, we choose to make the entire user model a single slice of the global store.

**Quaternion Model**

For tutorial purposes, a quaternion consists of four numbers, _w, i, j,_ and _k_. The student understands these to be the real part, and the amounts of the vector along the _i, j,_ and _k_ axes, respectively. As developers, we don’t care. It’s just four numbers, always provided in a pre-defined order. Based on past applications, I have supplied a class to organize this data, named after an infamous Star Trek TNG character :)

_/src/app/shared/definitions/Q.ts_

```
/**
 * Manage quaternion data
 *
 * @author Jim Armstrong
 *
 * @version 1.0
 */
export class Q
{
  public id  = '';

  protected _w = 0;
  protected _i = 0;
  protected _j = 0;
  protected _k = 0;

  /**
   * Construct a new Q
   *
   * @param wValue Real part of the quaternion
   *
   * @param iValue i-component of the quaternion
   *
   * @param jValue j-component of the quaternion
   *
   * @param kValue k-component of the quaternion
   *
   * @param _id (optional) id associated with these values
   */
  constructor(wValue: number, iValue: number, jValue: number, kValue: number, _id?: string)
  {
    this.w = wValue;
    this.i = iValue;
    this.j = jValue;
    this.k = kValue;

    if (_id !== undefined && _id != null && _id !== '') {
      this.id = _id;
    }
  }

  /**
   * Access the w-value of the quaternion
   */
  public get w(): number { return this._w; }

  /**
   * Assign the w-value of the quaternion
   *
   * @param {number} value
   */
  public set w(value: number)
  {
    if (!isNaN(value) && isFinite(value)) {
      this._w = value;
    }
  }

  /**
   * Access the i-value of the quaternion
   */
  public get i(): number { return this._i; }

  /**
   * Assign the i-value of the quaternion
   *
   * @param {number} value
   */
  public set i(value: number)
  {
    if (!isNaN(value) && isFinite(value)) {
      this._i = value;
    }
  }

  /**
   * Assign the i-value
   *
   * @param {number} value
   */
  public set i(value: number)
  {
    if (!isNaN(value) && isFinite(value)) {
      this._i = value;
    }
  }

  /**
   * Assign the k-value
   *
   * @param {number} value of the quaternion
   */
  public set j(value: number)
  {
    if (!isNaN(value) && isFinite(value)) {
      this._j = value;
    }
  }

  /**
   * Access the j-value of quaternion
   */
  public get j(): number { return this._j; }

  public get k(): number { return this._k; }

  /**
   * Assign the k-value
   *
   * @param {number} value
   */
  public set k(value: number)
  {
    if (!isNaN(value) && isFinite(value)) {
      this._k = value;
    }
  }

  /**
   * Clone this holder
   *
   * @returns {Q} Copy of current quaternion values holder
   */
  public clone(): Q
  {
    return new Q(this._w, this._i, this._j, this._k, this.id);
  }
}
```

**Calculator Model**

The calculator consists of two input quaternions, a result quaternion, operation buttons for add/subtract/multiply/divide, and to/from memory buttons.

The state of the entire calculator is represented in _/src/app/shared/definitions/QCalc.ts_

```
/**
 * Model a section of the quaternion calculator store that pertains to all basic calculator actions
 *
 * @author Jim Armstrong (www.algorithmist.net)
 *
 * @version 1.0
 */
import { Q } from './Q';

export class QCalc
{
  public q1: Q;
  public q2: Q;
  public result: Q;
  public memory: Q | null;
  public op: string;

  constructor()
  {
    this.q1     = new Q(0, 0, 0, 0);
    this.q2     = new Q(0, 0, 0, 0);
    this.result = new Q(0, 0, 0, 0);
    this.memory = null;
    this.op     = 'none';
  }

  /**
   * Clone this container
   */
  public clone(): QCalc
  {
    const q: QCalc = new QCalc();

    q.q1     = this.q1.clone();
    q.q2     = this.q2.clone();
    q.result = this.result.clone();
    q.op     = this.op;
    q.memory = this.memory ? this.memory.clone() : null;

    return q;
  }
}
```

**Test Model**

The test section of the application is only a placeholder in Part I of this series. The test is not formally modeled at this time.

After examining these models, it seems that the application store consists of three slices, _user, calculator,_ and _test,_ where the latter slice is optional as the student is not required to take the test until they are ready.

These slices are currently represented in _/src/app/shared/calculator-state.ts_

```
import { User  } from './definitions/user';
import { QCalc } from './definitions/QCalc';
export interface CalcState
{
  user: User;
  calc: QCalc;
  test?: any;
}
```

**Features**

The application divides nicely into three views or features, namely login, practice with calculator, and assessment test. These can each be represented by a feature module in the application. Each feature also contributes something to the global store.

The login screen contributes the _user_ slice. The ‘practice with calculator’ view contributes the _QCalc_ or _calculator_  slice of the store. The assessment _test_ contributes the test slice of the global store.

A feature of _@ ngrx/store_ version 10 is that the global store need not be defined in its entirety in the main app module. The store may be dynamically constructed as features are loaded into the application.

The _/src/app/features_ folder contains a single folder for each feature module of the application. Before deconstructing each feature, let’s look at the high-level application structure in _/src/app/app.module.ts,_

```
/**
 * Main App module for the quaternion application (currently at Part I)
 *
 * @author Jim Armstrong
 *
 * @version 1.0
 */
import { BrowserModule           } from '@angular/platform-browser';
import { NgModule                } from '@angular/core';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';

import { StoreModule } from '@ngrx/store';

import { MatTabsModule      } from '@angular/material/tabs';

import { AppRoutingModule   } from './app-routing.module';
import { LoginModule        } from './features/login-page/login.module';
import { CalculatorModule   } from './features/quaternion-calculator/calculator.module';

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

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    BrowserModule,
    BrowserAnimationsModule,
    AppRoutingModule,
    MatTabsModule,
    StoreModule.forRoot({}),
    LoginModule,
    CalculatorModule,
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule { }
```

Notice that unlike other _@ ngrx/store_ tutorials you may have seen in the past, the global store is empty,

**StoreModule.forRoot({}),**

In past examples of using _@ ngrx/store_ for just the quaternion calculator, I defined the reducers for each slice,

```
import { QInputs } from "./QInputs";
import { QMemory } from "./QMemory";

export interface CalcState
{
  inputs: QInputs;

  memory: QMemory;
}

import { ActionReducerMap } from '@ ngrx/store';
import {inputReducer, memoryReducer} from "../reducers/quaternion.reducers";

export const quaternionCalcReducers: ActionReducerMap<CalcState> =
{
  inputs: inputReducer,
  memory: memoryReducer
};
```

and then imported _quaternionCalcReducers_ into the main app module, followed by

```
@NgModule({
  declarations: APP_DECLARATIONS,
  imports: [
    PLATFORM_IMPORTS,
    MATERIAL_IMPORTS,
    StoreModule.forRoot(quaternionCalcReducers)
  ],
  providers: APP_SERVICES,
  bootstrap: [AppComponent]
})
```

The current application begins with an empty store. The application’s features build up the remainder of the store as they are loaded.

And, on the subject of loading, here is the main app routing module,

```
import { NgModule } from '@angular/core';
import {
  Routes,
  RouterModule
} from '@angular/router';

import { CalculatorComponent } from './features/quaternion-calculator/calculator/calculator.component';
import { LoginComponent      } from './features/login-page/login/login.component';

const calculatorRoutes: Routes = [
  { path: 'calculator', component: CalculatorComponent},

  { path: 'login', component: LoginComponent},

  { path: 'test',  loadChildren: () => import('./features/test/test.module').then(m => m.TestModule)},

  { path: '', redirectTo: 'calculator', pathMatch: 'full'},
];

@NgModule({
  imports: [
    RouterModule.forRoot(calculatorRoutes)
  ],
  exports: [RouterModule]
})
export class AppRoutingModule { }
```

Part I of this tutorial simulates a realistic situation where we don’t have a full, signed-off set of specifications for login and we may not even have complete designs. Login is deferred until a later sprint and the application currently displays the calculator by default. Note that the calculator is always available to the student when the application loads.

The test is always optional, so the test module is lazy-loaded.

Our deconstruction begins with the login feature.

**Login Feature** (_/src/app/features/login_)

This folder contains a _login-page_ folder for the Angular Version 10 login component as well as the following files:

* _login.actions.ts_ (actions for the login feature)
* _login.module.ts_ (Angular feature model for login)
* _login.reducer.ts_ (reducer for the login feature)

Unlike applications or tutorials you may have worked on in the past, a feature module may now contain store information, component, and routing definitions.

My personal preference is to consider development in the order of actions, reducers, and then module definition.

_Login actions_

These actions are specified in _/src/app/features/login-page/login.actions.ts,_

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

import { User } from '../../shared/definitions/user';

export const Q_AUTH_USER = createAction(
  '[Calc] Authenticate User'
);

export const Q_USER_AUTHENTICATED = createAction(
  '[Calc] User Authenticated',
  props<{user: User}>()
);
```

The expectation is that the username/password input at login are to be sent to an authentication service. That service returns a _User_ object, part of which is a boolean to indicate whether or not that specific login is authorized for the application.

If you are not used to seeing _props_ as shown above, this is the _@ ngrx/store_ version 10 mechanism to specify metadata (payloads in the past) to help process the action. This approach provides better type safety, which I can appreciate as an absent-minded mathematician who has messed up a few payloads in my time :)

_Login reducers_

Reducers modify the global store in response to specific actions and payloads. Since the global store is constructed feature-by-feature, each feature module contains a _feature key_ that is used to uniquely identify the slice of the global store covered by that feature.

The reducer file also defines an initial state for its slice of the store. This is illustrated in the very simple reducer from _/src/app/features/login-page/login.reducer.ts,_

```
import {
  createReducer,
  on
} from '@ngrx/store';

import * as LoginActions from './login.actions';

import { User } from '../../shared/definitions/user';

const initialLoginState: User = {
  first: '',
  last: '',
  classID: '101',
  studentID: '007',
  authorized: true
};

// Feature key
export const userFeatureKey = 'user';

export const loginReducer = createReducer(
  initialLoginState,

  on( LoginActions.Q_AUTHENTICATE_USER, (state, {user}) => ({...state, user}) ),
);
```

Spread operators may be convenient, but always be a bit cautious about frequent use of shallow copies, especially when Typescript classes and more complex objects are involved. You will note that all my Typescript model classes contain _clone()_ methods and frequent cloning is performed before payloads are even sent to a reducer. This can be helpful for situations where one developer works on a component and another works on reducers. Sloppy reducers can give rise to the infamous ‘can not modify private property’ error in an NgRx application.

_Login feature module_

The login component is eagerly loaded. The login route is already associated with a component in the main app routing module. The login feature module defines the slice of the global store that is created when the login module is loaded.

_/src/app/features/login-page/login.module.ts_

```
import { NgModule } from '@angular/core';

import { StoreModule } from '@ngrx/store';

import * as fromLogin from './login.reducer';

@NgModule({
  imports:
    [
      StoreModule.forFeature(fromLogin.userFeatureKey, fromLogin.loginReducer),
    ],
  exports: []
})
export class LoginModule {}
```

Since _LoginModule_ is imported into the main app module, the _user_ slice of the global store is defined as soon as the application loads.

The test module, however, is lazy-loaded, so its implementation is slightly more involved.

**Test Feature** (_/src/app/features/test_)

This folder contains the test folder for the Angular component files as well as feature-related files. As with login, the feature-specific files are

* _test.actions.ts_ (actions for the test feature)
* _test.module.ts_ (Angular feature model for test)
* _test.reducer.ts_ (reducer for the login feature)

And, as before, these are deconstructed in the order, actions, reducers, and then feature module.

_Test Actions_

As of Part I of this tutorial, we anticipate four test actions,

1 — Request a list of test questions from a server (_Q_GET_TEST_)

2 — Indicate that the test has begun (_Q_BEGIN_TEST_)

3 — Send a collection of scored test results back to the server (_Q_SCORE_TEST_)

4 — Send test results back to the server (_Q_SEND_TEST_RESULTS_)

The second action is needed to ensure that the calculator can not be used once the test begins.

_/src/app/features/test/test.actions.ts_

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

// Feature key
export const textFeatureKey = 'test';

export const Q_GET_TEST = createAction(
  '[Calc] Get Test'
);

export const Q_BEGIN_TEST = createAction(
  '[Calc] Begin Test',
  props<{startTime: Date}>()
);

export const Q_SCORE_TEST = createAction(
  '[Calc] Score Test',
  props<{results: Array<any>}>()
);

export const Q_SEND_TEST_RESULTS = createAction(
  '[Calc] Send Test Results',
  props<{endTime: Date, results: Array<any>}>()
);
```

A _feature key_ is again used as a unique identifier for the _test_ slice of the global store. Part I of this tutorial simulates a situation where we have not been given the model for a collection of test questions. Nor do we understand how to extend that model to include scored results. Typings applied to the payload for the final two actions are simply placeholders.

```
<hint>
  Stories typically have unique identifiers in tracking systems.  Consider 
using the tracking id as part of the action name. In the case of Pivotal 
Tracker, for example, 'ADD [PT 10472002]'. This string contains the 
operation, i.e. 'ADD', along with the Pivotal Tracker ID for the story. 
This allows other developers to quickly relate actions to application 
requirements.
</hint>
```

_Test Reducers_

The current test reducer and initial test state are placeholders for Part I of this tutorial.

_/src/app/features/test/test.reducer.ts_

```
import * as TestActions from './test.actions';

import {
  createReducer,
  on
} from '@ngrx/store';

// At Part I, we don't yet know the model for a test question
const initialTestState: {test: Array<string>} = {
  test: new Array<any>()
};

// Feature key
export const testFeatureKey = 'test';

const onGetTest = on (TestActions.Q_GET_TEST, (state) => {
  // placeholder - currently does nothing
  return { state };
});

export const testReducer = createReducer(
  initialTestState,
  onGetTest
);
```

_Test Module_

The test module defines routes **and** adds the _test_ slice to the global store,

_/src/app/features/test/test.module.ts_

```
import { NgModule     } from '@angular/core';
import { CommonModule } from '@angular/common';
import {
  RouterModule,
  Routes
} from '@angular/router';

import { StoreModule } from '@ngrx/store';

import * as fromTest from './test.reducer';

import { TestComponent } from './test/test.component';

import { AuthGuard } from '../../shared/guards/auth-guard';

const routes: Routes = [
  { path: '', component: TestComponent, canActivate: [AuthGuard] }
];

@NgModule({
  declarations: [
    TestComponent
  ],
  imports:
    [
      CommonModule,
      StoreModule.forFeature(fromTest.testFeatureKey, fromTest.testReducer),
      RouterModule.forChild(routes)
    ],
  providers: [AuthGuard],
  exports: [
  ]
})
export class TestModule {}
```

Notice that a route guard has been added to the default child route. This guard ensures that the test route may not be directly requested unless the user is currently authorized. The guard will be fully implemented in part IV of this tutorial. The current implementation simply hardcodes an _authenticated_ flag, so that any user is considered authorized.

**Calculator Feature** (_/src/app/features/quaternion-calculator_)

The calculator is the main focus of Part I of this tutorial, so its action list is complete,

_/src/app/features/quaternion-calculator/calculator.actions.ts_

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


import { Q } from '../../shared/definitions/Q';

// Actions
export const Q_UPDATE = createAction(
  '[Calc] Update',
  props<{id: string, q: Q}>()
);

export const Q_ADD = createAction(
  '[Calc] Add',
  props<{q1: Q, q2: Q}>()
);

export const Q_SUBTRACT = createAction(
  '[Calc] Subtract',
  props<{q1: Q, q2: Q}>()
);

export const Q_MULTIPLY = createAction(
  '[Calc] Multiply',
  props<{q1: Q, q2: Q}>()
);

export const Q_DIVIDE = createAction(
  '[Calc] Divide',
  props<{q1: Q, q2: Q}>()
);

export const Q_CLEAR = createAction(
  '[Calc] Clear',
);

export const TO_MEMORY = createAction(
  '[Calc] To_Memory',
  props<{q: Q, id: string}>()
);

export const FROM_MEMORY = createAction(
  '[Calc] From_Memory',
  props<{id: string}>()
);
```

Note that all payloads involving quaternions use the generic ‘Q’ class. This allows the reducer the greatest flexibility in implementing calculator operations. Before we look at the reducer, though, recall that the Typescript Math Toookit _TSMT$Quaternion_ class is used to implement all quaternion arithmetic. In the future, though, a different class (or collection of pure functions) might be used.

With future changes in mind, the _Adapter Pattern_ is applied to create an intermediary between the generic ‘Q’ structure and the code responsible for quaternion arithmetic. This helper class is located in _/src/app/shared/libs/QCalculations.ts_

```
import { TSMT$Quaternion } from './Quaternion';
import { Q              } from '../definitions/Q';

export class QCalculations
{
  protected static readonly Q1: TSMT$Quaternion = new TSMT$Quaternion();
  protected static readonly Q2: TSMT$Quaternion = new TSMT$Quaternion();

  constructor()
  {
    // empty
  }

  /**
   * Add two quaternions
   *
   * @param q1 4-tuple representing first input quaternion
   *
   * @param q2 4=tuple representing second input quaternion
   */
  public static add(q1: Q, q2: Q): Q
  {
    QCalculations.Q1.fromArray(q1.w, q1.i, q1.j, q1.k);
    QCalculations.Q2.fromArray(q2.w, q2.i, q2.j, q2.k);

    QCalculations.Q1.add(QCalculations.Q2);

    const values: Array<number> = QCalculations.Q1.toArray();

    return new Q(values[0], values[1], values[2], values[3]);
  }

  /**
   * Subtract two quaternions
   *
   * @param q1 4-tuple representing first input quaternion
   *
   * @param q2 4=tuple representing second input quaternion
   */
  public static subtract(q1: Q, q2: Q): Q
  {
    QCalculations.Q1.fromArray(q1.w, q1.i, q1.j, q1.k);
    QCalculations.Q2.fromArray(q2.w, q2.i, q2.j, q2.k);

    QCalculations.Q1.subtract(QCalculations.Q2);

    const values: Array<number> = QCalculations.Q1.toArray();

    return new Q(values[0], values[1], values[2], values[3]);
  }

  /**
   * Mutiply two quaternions
   *
   * @param q1 4-tuple representing first input quaternion
   *
   * @param q2 4=tuple representing second input quaternion
   */
  public static multiply(q1: Q, q2: Q): Q
  {
    QCalculations.Q1.fromArray(q1.w, q1.i, q1.j, q1.k);
    QCalculations.Q2.fromArray(q2.w, q2.i, q2.j, q2.k);

    QCalculations.Q1.multiply(QCalculations.Q2);

    const values: Array<number> = QCalculations.Q1.toArray();

    return new Q(values[0], values[1], values[2], values[3]);
  }

  /**
   * Divide two quaternions
   *
   * @param q1 4-tuple representing first input quaternion
   *
   * @param q2 4=tuple representing second input quaternion
   */
  public static divide(q1: Q, q2: Q): Q
  {
    QCalculations.Q1.fromArray(q1.w, q1.i, q1.j, q1.k);
    QCalculations.Q2.fromArray(q2.w, q2.i, q2.j, q2.k);

    QCalculations.Q1.divide(QCalculations.Q2);

    const values: Array<number> = QCalculations.Q1.toArray();

    return new Q(values[0], values[1], values[2], values[3]);
  }
}
```

This class currently uses _TSMT$Quaternion_ for quaternion arithmetic. If another library is used in the future, it is not necessary to change reducer code; only the helper class need be modified. This helper or adapter class can also have its own set of tests, which serves to strengthen tests already present for reducers.

Now, we can deconstruct the calculator reducers. The _createReducer()_ method from _@ ngrx/store_ seems so simple with one-line reducers in a scoreboard or counter application. The quaternion calculator is different in that reduction for each calculator operation is more involved.

```
import {
  createReducer,
  on,
  createSelector,
  createFeatureSelector
} from '@ngrx/store';

import * as CalculatorActions from './calculator.actions';

import { QCalc         } from '../../shared/definitions/QCalc';
import { QCalculations } from '../../shared/libs/QCalculations';
import { Q             } from '../../shared/definitions/Q';
import { CalcState     } from '../../shared/calculator-state';

const initialCalcState: {calc: QCalc} = {
  calc: new QCalc()
};

function calcFatory(calculator: QCalc, q1: Q, q2: Q, result: Q): QCalc
{
  const newCalculator: QCalc = new QCalc();

  newCalculator.q1     = q1.clone();
  newCalculator.q2     = q2.clone();
  newCalculator.result = result.clone();
  newCalculator.op     = calculator.op;
  newCalculator.memory = calculator.memory ? calculator.memory : null;

  return newCalculator;
}

// Feature key
export const calculatorFeatureKey = 'calc';

// Selectors
export const getCalcState = createFeatureSelector<CalcState>(calculatorFeatureKey);

export const getCalculator = createSelector(
  getCalcState,
  (state: CalcState) => state ? state.calc : null
);

// Calculator Reducers
const onUpdate = on (CalculatorActions.Q_UPDATE, (state, {id, q}) => {
  const calculator: CalcState = state as CalcState;

  const newCalculator: QCalc = calculator.calc.clone();

  if (id === 'q1')
  {
    // update first quaternion
    newCalculator.q1 = q.clone();
  }
  else
  {
    // update second quaternion
    newCalculator.q2 = q.clone();
  }

  return { ...calculator.user, calc: newCalculator };
});

const onAdd = on (CalculatorActions.Q_ADD, (state, {q1, q2}) => {
  const calculator: CalcState = state as CalcState;

  const q: Q = QCalculations.add(q1, q2);

  return { ...calculator.user, calc: calcFatory(calculator.calc, q1, q2, q) };
});

const onSubtract = on (CalculatorActions.Q_SUBTRACT, (state, {q1, q2}) => {
  const calculator: CalcState = state as CalcState;

  const q: Q = QCalculations.subtract(q1, q2);

  return { ...calculator.user, calc: calcFatory(calculator.calc, q1, q2, q) };
});

const onMultiply = on (CalculatorActions.Q_MULTIPLY, (state, {q1, q2}) => {
  const calculator: CalcState = state as CalcState;

  const q: Q = QCalculations.multiply(q1, q2);

  return { ...calculator.user, calc: calcFatory(calculator.calc, q1, q2, q) };
});

const onDivide = on (CalculatorActions.Q_DIVIDE, (state, {q1, q2}) => {
  const calculator: CalcState = state as CalcState;

  const q: Q = QCalculations.divide(q1, q2);

  return { ...calculator.user, calc: calcFatory(calculator.calc, q1, q2, q) };
});

const onToMemory = on (CalculatorActions.TO_MEMORY, (state, {q}) => {
  const calculator: CalcState = state as CalcState;

  const newCalculator  = calculator.calc.clone();
  newCalculator.memory = q.clone();

  return { ...calculator.user, calc: newCalculator };
});

const onFromMemory = on (CalculatorActions.FROM_MEMORY, (state, {id}) => {
  const calculator: CalcState = state as CalcState;

  const newCalculator  = calculator.calc.clone();

  switch (id)
  {
    case 'Q_1':
      newCalculator.q1 = newCalculator.memory != null ? newCalculator.memory.clone() : null;
      break;

    case 'Q_2':
      newCalculator.q2 = newCalculator.memory != null ? newCalculator.memory.clone() : null;
      break;

    default:
      // no action taken at this time as index is invalid; perhaps throw an error
  }

  return { ...calculator.user, calc: newCalculator };
});

const onClear = on (CalculatorActions.Q_CLEAR, (state) => {
  const calculator: CalcState = state as CalcState;

  return { ...calculator.user, calc: new QCalc() };
});

export const calculatorReducer = createReducer(
  initialCalcState,
  onUpdate,
  onAdd,
  onSubtract,
  onMultiply,
  onDivide,
  onToMemory,
  onFromMemory,
  onClear
);
```

Let’s look at one action, calculator addition. The second argument to the _@ ngrx/store on()_ method is the combination of prior store and payload. The payload shape is described in the action, so examine the action and reducer side-by-side:

```
export const Q_ADD = createAction(
  '[Calc] Add',
  props<{q1: Q, q2: Q}>()
);
.
.
.
const onAdd = on (CalculatorActions.Q_ADD, (state, **{q1, q2}**) => {
  const calculator: CalcState = state as CalcState;

  const q: Q = QCalculations.add(q1, q2);

  return { ...calculator.user, calc: calcFatory(calculator.calc, q1, q2, q) };
});
```

Other calculation computations are handled in a similar manner. Note that an id is involved in moving quaternion data to and from calculator memory and this id is specified in the quaternion calculator template,

_/src/app/features/quaternion-calculator/calculator/calculator.component.html_

```
.
.
.
<div class="card-center">
  <app-quaternion id="q1" [inputDisabled]="inputDisabled" (qChanged)="onQuaternionChanged($event)"></app-quaternion>
</div>
<app-memory id="Q_1" (memTo)="onToMemory($event)" (memFrom)="onFromMemory($event)"></app-memory>
.
.
.
```

Recall that the _QCalc_ class is used to represent the calculator slice of the global store, so initial calculator state is simply a new instance of this class,

```
const initialCalcState: {calc: QCalc} = {
  calc: new QCalc()
};
```

and, the reducer for all calculator actions is defined at the end of the process,

```
export const calculatorReducer = createReducer(
  initialCalcState,
  onUpdate,
  onAdd,
  onSubtract,
  onMultiply,
  onDivide,
  onToMemory,
  onFromMemory,
  onClear
);
```

The calculator route is eagerly loaded and already specified in the main app routing module, so the calculator module only handles adding the calculator section or slice to the global store,

_/src/app/features/quaternion-calculator/calculator.module.ts_

```
.
.
.

@NgModule({
  declarations: [
    CalculatorComponent,
    QuaternionComponent,
    MemoryComponent,
    ResultComponent,
  ],
  imports:
    [
      CommonModule,
      FormsModule,
      MAT_IMPORTS,
      StoreModule.forFeature(fromCalculator.calculatorFeatureKey, fromCalculator.calculatorReducer),
    ],
  exports: [
  ]
})
export class CalculatorModule {}
```

This process seems intimidating at first, but only if you try to absorb everything at one time. I personally like the _build-the-store-by-feature_ approach illustrated above, as it’s very intuitive. Remember the order _actions, reducers, module,_ and try working on just one action and one reducer function at a time. That’s exactly what I did when preparing this tutorial. I worked on the ADD action first. Then, I implemented SUBTRACT. I noticed some repeated code and made the reducers more DRY. Then, the remainder of the calculator reducers came together in short order.

**Store Selection**

Components query the store (or some subset) and generally reflect those values directly into the component’s template. This application is different in that some components follow that exact model while others such as the calculator maintain an internal copy of the _calc_ slice of the store. That component’s template does not directly reflect any of the _calc_ values. It maintains a constant sync with the ‘q1’ and ‘q2’ input quaternions in order to dispatch copies of them as payloads when the user clicks on one of the operations (add/subtract/multiply/divide).

_@ ngrx/store_ provides the ability to direct-select a named slice from the store and assign the result to an _Observable_. This feature is illustrated in the counter app in the _@ ngrx/store_ docs.

Store _selectors_ may also be created, which direct-select exact slices of the store or subsets of those slices. This process is illustrated in the calculator reducer file, _/src/app/features/quaternion-calculator/calculator.reducer.ts,_

```
.
.
.
export const getCalcState = createFeatureSelector<CalcState>(calculatorFeatureKey);

export const getCalculator = createSelector(
  getCalcState,
  (state: CalcState) => state ? state.calc : null
);

// Select result quaternion values - combine these as an exercise
export const getResultW = createSelector(
  getCalcState,
  (state: CalcState) => state ? (state.calc.result ? state.calc.result.w : null) : null
);

export const getResultI = ((createSelector(((
  getCalcState,
  (state: CalcState) => state ? (state.calc.result ? state.calc.result.i : null) : null
);

export const getResultJ = createSelector(
  getCalcState,
  (state: CalcState) => state ? (state.calc.result ? state.calc.result.j : null) : null
);

export const getResultK = createSelector(
  getCalcState,
  (state: CalcState) => state ? (state.calc.result ? state.calc.result.k : null) : null
);
```

One selector fetches the calc state of the global store while the remaining four selectors query the individual values of the result quaternion.

A classic subscription model is used to handle updates from the store inside the calculator component,

_/src/app/features/quaternion-calculator/calculator/calculator.component.ts_

```
protected _calc$: Subject<boolean>;
.
.
.
this._store.pipe(
  select(getCalculator),
  takeUntil(this._calc$)
)
.subscribe( calc => this.__onCalcChanged(calc));
```

The __onCalcChanged()_ method simply syncs the class variable with the store,

```
protected __onCalcChanged(calc: QCalc): void
{
  if (calc) {
    this._qCalc = calc.clone();
  }
}
```

and the unsubscribe is handled in the on-destroy lifecycle hander,

```
public ngOnDestroy(): void
{
  this._calc$.next(true);
  this._calc$.complete();
}
```

Next, look at the result quaternion code in _/src/app/shared/components/result/result.component.ts_

The result quaternion values _[w, i, j, k]_ are directly reflected in the template and can be easily updated with the just-created selectors and an _async_ pipe.

```
.
.
.
import {
  getResultW,
  getResultI,
  getResultJ,
  getResultK
} from '../../../features/quaternion-calculator/calculator.reducer';

@Component({
  selector: 'app-result',

  templateUrl: './result.component.html',

  styleUrls: ['./result.component.scss']
})
export class ResultComponent
{
  // Observables of quaternion values that are directly reflected in the template
  public w$: Observable<number>;
  public i$: Observable<number>;
  public j$: Observable<number>;
  public k$: Observable<number>;

  constructor(protected _store: Store<CalcState>)
  {
    this.w$ = this._store.pipe( select(getResultW) );
    this.i$ = this._store.pipe( select(getResultI) );
    this.j$ = this._store.pipe( select(getResultJ) );
    this.k$ = this._store.pipe( select(getResultK) );
  }
}
```

_/src/app/shared/components/result/result.component.html,_

```
<div>
  <mat-form-field class="qInput">
    <input matInput type="number" value="{{w$ | async}}" readonly />
  </mat-form-field>

  <mat-form-field class="qInput qSpaceLeft">
    <input matInput type="number" value="{{i$ | async}}" readonly />
  </mat-form-field>

  <mat-form-field class="qInput qSpaceLeft">
    <input matInput type="number" value="{{j$ | async}}" readonly />
  </mat-form-field>

  <mat-form-field class="qInput qSpaceLeft">
    <input matInput type="number" value="{{k$ | async}}" readonly />
  </mat-form-field>
</div>
```

**Result**

This is the initial view for Part I after building the application.

![Screenshot of an online quiz. There are two tabs to the quiz, one opened one closed. The opened tab is labeled "Practice With Calculator", the closed tab is labeled "Assessment Test". The test in the opened tab is named "Quaternion Calculator".](https://miro.medium.com/max/518/1*Sxg_YoUXqm8r5qcD0RuHOw.png)
###### Quaternion Application Initial View

Now, if you were expecting great design from a mathematician, then you probably deserve to be disappointed :)

Experiment with quaternion arithmetic and have fun. Be warned, however, multiplication and division are not what you might expect.

**Summary**

Applications are rarely built all at once. They are often created small sections at a time (usually in organized sprints). Not everything will be defined in full detail at the onset of a project, so the global store may evolve over time. I hope this tutorial series introduces the NgRx suite in a manner that is less like other tutorials and more like how you would use the framework in a complete application.

In Part II, we receive the test definition from the back-end team and a proposal for a set of service calls to implement the test view. We will mock a back end using an _HTTP Interceptor_ and fill out the _test_ slice of the global store. _@ ngrx/effects_ will be used to handle service interactions.

I hope you found something helpful from this tutorial and best of luck with your Angular efforts!

## **ng-conf: The Musical is coming**
ng-conf: The Musical is a two-day conference from the ng-conf folks coming on April 22nd & 23rd, 2021. Check it out at [ng-conf.org](https://www.2021.ng-conf.org/)

###### Thanks to Michi DeWitt.