Stop using NgRx in Angular
[update : more example : Angular State Management … without NgRx]
Do you really think that Google’s engineers are so inexperienced that they need an external library, namely NgRx, produced by some geeks, to make their framework Angular efficient ? Do you really think that the inventors of the V8 javascript machine and Node.js are so light in javascript that they are unable to produce a framework sufficient by itself to produce complex and heavy web apps ?
If so, you do not know the real power of the Angular technology, and the revolution that it has brought. Let me explain.
In javascript/HTML there is a major pain : the communication between components. A component, i.e. a piece of HTML attached to a piece of javascript, is natively independent of the other components. This is because the javascript does not include natively the notion of global variable for an entire HTML page. If you need it, you have to code a middleware that will connect the components, and thus create by yourself this notion of global variable between components, i.e. a 2-way data binding between components. We were doing so with a lot of javascript and JQuery code in the 2000’s, until some specialized libraries appeared, from which Redux is the most famous.
The problem with this classical way of doing things is that it requires a lot of complex coding. In the component A you shall declare the shared variable, and if it changes, what to do in each specific other components B, C, ... Furthermore, cascading, if the variable is changed in C, what shall be done on other components D, E, … Etc. Therefore the more complex your application is growing, heavier is your Redux code, with increasing complexity, so delays, maintenance, debugging, etc. But there is no choice, you have to do so, because the javascript forces you to. This is why some frameworks are still using this technology, like React or Vue, however now implementing their own Redux like.
Here comes Angular. Of course, like React or Vue, Angular ensures the 2-way data binding between the HTML and the javascript codes of each single component, but in addition it also ensures natively the 2-way data binding between components, by the means of its services. Consequently, it does not need any Redux like machinery, i.e. NgRx. Here is the revolution. Angular is based on an infinite loop, a setInterval of 10ms, that checks the whole application and propagates automatically the change of a variable in a component to the other components. To perform so, the variable has to be registered into an Angular service (@Injectable) and the components that need this variable must import this service. That is all.
By using NgRx you just superpose another 2-way data binding between components, to the native one of Angular, making the framework unable to work correctly. This is useless, time consuming and counterproductive.
If you are very attached to “control the state” of your application, and you deadly need the “immutability” provided by NgRx, please do it with the Angular technology, it is much simpler. Create an Angular service, and call it “store” if you are a Redux nostalgic, with provideIn: ‘root’, and register in it the variables that you need to describe the state of your application, then import this service into the components that are concerned with these variables. If one of the components changes a store variable, the store is updated as well as the other components, in 10ms. At any time your service store will represent the whole state of your application. You can then back it up, in a session object, localStorage, IndexedDB, or better on a server, so you will implement the concept of immutability, to be able to play back and forward your applications states. Indeed replacing the current store with a backup is only one line of code, this.store = backup[i], and the magic loop does the rest in the whole application, in 10ms.
The second major pain of the javascript developer is the asynchronicity. Everything is asynchronous in javascript, even a click on the screen. To handle it we have some tools, like the Promise, or today the super Promise that is the Observable. With a Redux like technology, so NgRx, the observables are resolved (subscribe) inside the components, no problem with that but it needs some coding, sometime complex, aside some memory leaks if the observable is not properly unsubscribed. No such thing in Angular, where the asynchronicity is better resolved inside the shared service, let say “store”, and the magic infinite loop does the rest, i.e. updates all the components that import “store”, in 10ms. No need to cope with observables in components, this is saving a lot of code and complexity, aside no possible memory leak.
Actually the aim of Angular is to ease the frontend developer’s work as much as possible. He must spend the less possible time on coding middlewares and observables, i.e. on the inside machinery, but spend a maximum possible time on producing some interface of high business value awaited by the end user. The end user does not care about the machinery under the application, he just needs the application as soon as possible, as cheap as possible. So in Angular, all is done to ease the production of high business value, and lower the coding complexity that has no direct business value. For Angular if you manipulate the observables (out of necessary exceptions) or code some Redux like store, you loose your time and performances, thus money. This is as simple as that.
Your application would be a tree, the store service would be the trunc, the components being the branches and leaves. The store service must be the backbone of your Angular application, therefore it must be built with a lot of care and structure. Of course, you can have many store services, each one dedicated to some parts of your application, and benefit of the notion of sigleton and providers, provided by the Angular services. It is up to you to make a smart use of this powerful technology.
To illustrate what I described here, I propose you a very simple application with 3 components sharing a common store service (in Angular 17). This example is also available on StackBlitz. Use it and look how the change of the user name in one component is instantly propagated to the other components, without having to code a single line of typescript.
user.ts
Let define a type User
export interface User {
firstname: string;
lastname: string;
age: number;
}
store.service.ts
We setup a service called store, and declare in it a global variable called user, of type User.
import { Injectable } from '@angular/core';
import { User } from '../classes/user';
@Injectable({
providedIn: 'root'
})
export class StoreService {
user: User | any;
}
api.service.ts
We setup an api service to mock a call to a server, so to implement the asynchronicity.
import { Injectable } from '@angular/core';
import { StoreService } from './store.service';
import { HttpClient } from '@angular/common/http';
@Injectable({
providedIn: 'root',
})
export class ApiService {
constructor(public store: StoreService, private http: HttpClient) {}
mockCallServer() {
this.http.get('/assets/users.json').subscribe((user: any) => {
// The observable send by the http.get is directly resolved
// into the shared service at the return of the api
this.store.user = user;
});
}
}
a.component.ts
This component is intended just to display the shared data imported from the store service.
import { Component } from '@anguhttps://stackblitz.com/edit/angular-17-starter-project-uyyx2b?file=src%2Fapp%2Fapp.component.tslar/core';
import { StoreService } from '../../services/store.service';
@Component({
selector: 'comp-a',
standalone: true,
imports: [],
template: `
<div class="container">
<b>Component A : displaying the imported store</b><br><br>
{{store.user.firstname}} {{store.user.lastname}}
</div>
`,
styleUrl: './a.component.css'
})
export class AComponent {
constructor(public store: StoreService) { }
}
b.component.ts
This component is intended to modify the shared data imported from the store service.
import { Component } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { StoreService } from '../../services/store.service';
@Component({
selector: 'comp-b',
standalone: true,
imports: [FormsModule],
template: `
<div class="container">
<b>Component B : changing the imported store</b><br><br>
Firstname : <input type="text" [(ngModel)]="store.user.firstname">
Lastname : <input type="text" [(ngModel)]="store.user.lastname">
</div>
`,
styleUrl: './b.component.css'
})
export class BComponent {
constructor(public store: StoreService) { }
}
c.component.ts
This component does not import the store, because it works with @Input/@Output, like a component comming from an external library or a design system. It can nonetheless be connected to the store in a way explained in te app.component.ts.
import { Component, Input, Output, EventEmitter } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { User } from '../../classes/user';
@Component({
selector: 'comp-in-out',
standalone: true,
imports: [FormsModule],
template: `
@if (user) {
<div class="container">
<b>Component C : independent Input/Output connected to the store</b><br><br>
Firstname : <input type="text" [(ngModel)]="user.firstname">
Lastname : <input type="text" [(ngModel)]="user.lastname">
</div>
}
`,
styleUrl: './in-out.component.css'
})
export class InOutComponent {
@Input() user!: User;
@Output() userChange = new EventEmitter<User>();
}
app.component.ts
The app component calls the api service to retrieve the asynchronous data, and displays the components when these data are available in the store service.
import { Component, OnInit } from '@angular/core';
import { AComponent } from './components/a/a.component';
import { BComponent } from './components/b/b.component';
import { InOutComponent } from './components/in-out/in-out.component';
import { StoreService } from './services/store.service';
import { ApiService } from './services/api.service';
@Component({
selector: 'my-comp',
standalone: true,
providers: [StoreService, ApiService],
imports: [
AComponent,
BComponent,
InOutComponent,
],
template: `
@if(!store.user) {
<div class="loading"> Please wait for loading ...</div>
}
@else {
<comp-a></comp-a>
<comp-b></comp-b>
<comp-in-out [(user)]="store.user"></comp-in-out>
}
`,
styleUrl: './app.component.scss',
})
export class AppComponent implements OnInit {
constructor(public store: StoreService, public api: ApiService) {}
ngOnInit() {
// Calling the mock server to feed the store.user
this.api.mockCallServer();
}
}
Now, try to reproduce this example with NgRx and observables, and compare both codes and time to develop. You will be convinced.
The conclusion is, stop using NgRx and stop juggling with observables, be intelligently lazy, use instead the full power of the Angular technology. Your boss, as well as the end users, will be so happy to congratulate you for your improvement in delivery delay, low cost, easy maintenance and evolutivity of your applications.
[update : more example : Angular State Management … without NgRx]