Hervé Le Cornec
Hervé Le Cornec

Reputation: 89

Why should I use Redux in Angular as far as Angular is two-way data binding?

As far as I understand Redux is mainly intended to enable the two-way data binding in a javascript app. This is very usefull for frameworks that are not two-way data binding, as React for instance. But why using it in Angular that is already natively two-way data binding ?

To illustrate my question here is the code that I use in native Angular to make a store that enables the communication of a variable state between two Angular components :

1) The store

import { Injectable } from '@angular/core';
@Injectable({
  providedIn: 'root'
})
export class StoreService {
  customer: any;
  constructor() { }
}

This store is a native Angular service where I only declare the variable customer (yes typing would be better but i want to shorten it as much as possible).

2) The asynchronous api service

import { Injectable } from '@angular/core';
import { StoreService } from './store.service'

@Injectable({
  providedIn: 'root'
})
export class ApiService {

  constructor( private store: StoreService ) { }

  getData() {
    setTimeout(()=> {
      this.store.customer = {
        name: 'Bob',
        age: 25
      }
    }, 2000);
  }
}

This api service has only one method getdata() that retrieves the customer data asynchronously. I could use an http.get method and in this case the code inside the setTimeout would be the code in the next() function of the observable subscribe. Note that I instanciate the return of the async process directly in the former store.

3) A component using the store

import { Component, OnInit } from '@angular/core';
import { ApiService } from '../api.service'
import { StoreService } from '../store.service'

@Component({
  selector: 'app-mycomponent',
  templateUrl: './mycomponent.component.html',
  styleUrls: ['./mycomponent.component.css']
})
export class MycomponentComponent implements OnInit {

  constructor(
    private api: ApiService,
    private store: StoreService
  ) { }

  ngOnInit() {

    this.api.getData();

  }
}

Note that except importing the services store and api, I use only one line of code, the one that call for data. This line would be useless if an other component, or any other service, would have already populated the store (see the second component below)

4) The HTML template of the component

<ul>
  <li>Name : {{store.customer?.name}}</li>
  <li>Age : {{store.customer?.age}}</li>
</ul>

Note that I use directly the store in the template to ensure the two-way data binding with the other components that also imports the same store. Note the use of the elvis operator ?. that manages the async variable store.customer.

5) An other component, that modifies the store

import { Component, OnInit } from '@angular/core';
import { StoreService } from '../store.service'

@Component({
  selector: 'app-myothercomponent',
  templateUrl: './myothercomponent.component.html',
  styleUrls: ['./myothercomponent.component.css']
})
export class MyothercomponentComponent implements OnInit {   
  constructor(private store: StoreService) { }
}

I only import the store, I have no need for any other line of code.

6) The HTML template of the former component

<p>
  <input type="text" [(ngModel)]="store.customer && store.customer.name">
</p>

Note the special way to handle the asynchronism because of the use of ngModel. By the way think to import the FormsModule in dorder to handle the input in the HTML.

Now change the value of the customer name in the second component and you will see its direct and instantaneous change in the first component.

This two-way data binding of Angular is so powerfull, and so simple to setup and use, that I really wonder why I should rather use Redux, or NgRx. Could you please explain me why I should ?

Thanks for help.

Upvotes: 8

Views: 1556

Answers (4)

user12207064
user12207064

Reputation:

You are actually right.

I have just tried it, assigned the result of a http subscription to the store's member variable, populated this variable, updated the values, set them to [] and it all worked.

I believe we're taught the wrong way to do things from the start.

Next step I'll try it with routing and more components. Update, it works with routing as well. I don't know why I thought so complicated in the past with BehaviorSubject etc.

app.component.html

<dl *ngFor="let post of posts">
  <dd>{{post.body}}</dd>
</dl>

<button (click)="getPosts()">Posts</button>
<button (click)="getComments()">Comments</button>
<button (click)="zero()">Zero</button>

app.component.ts

import { Component } from '@angular/core';
import {StoreService} from './store.service';
import {BackendService} from './backend.service';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent {
  constructor(
    private store: StoreService,
    private backend: BackendService,
  ) {
  }

  get posts() {
    return this.store.posts;
  }

  set posts(posts) {
    this.store.posts = posts;
  }

  getPosts() {
    this.backend.getPosts();
  }

  getComments() {
    this.backend.getComments();
  }

  zero() {
    this.store.posts = [];
  }
}

app.module.ts

import {BrowserModule} from '@angular/platform-browser';
import {NgModule} from '@angular/core';

import {AppComponent} from './app.component';
import {HttpClientModule} from '@angular/common/http';

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    HttpClientModule
  ],
  providers: [],
  bootstrap: [AppComponent]
})
export class AppModule {
}

backend.service.ts

import { Injectable } from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {StoreService} from './store.service';

@Injectable({
  providedIn: 'root'
})
export class BackendService {

  constructor(
    private http: HttpClient,
    private store: StoreService,
    ) { }

  getPosts() {
    this.http.get<object[]>('https://jsonplaceholder.typicode.com/posts').subscribe(posts => this.store.posts = posts);
  }

  getComments() {
    this.http.get('https://jsonplaceholder.typicode.com/comments').subscribe(comments => this.store.posts = comments);
  }
}

store.service.ts

import { Injectable } from '@angular/core';

@Injectable({
  providedIn: 'root'
})
export class StoreService {
  posts;

  constructor() { }
}

Upvotes: 2

mbojko
mbojko

Reputation: 14679

It's hard to illustrate the Flux pattern's advantages with a small project, of complexity comparable to "Hello, World!". Just as with a simple scenario "fetch one JSON and put it on screen" it's hard to argue RxJS's virtues - not only promises are perfectly adequate, but even good old callbacks will do the job.

Same with state management. I've recently initiated migration of my project from the "spaghetti mess of distributed state hidden in several services and components" architecture to NGXS. The fact that, for example, the component can just @Select(RelevantStore.relevantProperty) in one line, instead of consulting router state and a couple of services along the way, improves codebase maintainability dramatically.

Upvotes: 1

Reactgular
Reactgular

Reputation: 54751

As far as I understand Redux is mainly intended to enable the two-way data binding in a javascript app. This is very usefull for frameworks that are not two-way data binding, as React for instance. But why using it in Angular that is already natively two-way data binding?

That statement is wrong, and so everything else after it is based upon an incorrect impression of what problem Redux solves.

It has nothing to do with data binding, but you're close to the problem it solves.

Redux solves the problem that let x = {y: 2}; x.y = 3; hides a mutation of a value, and everything else in your JavaScript application will have a difficult time knowing when x.y changes.

Historically, frameworks like AngularJS 1.x used watchers that would compare a template expression with its previous value to see if it has changed, but if you're template is referencing x and the inner value x.y has changed, then the watcher doesn't see it.

React has only one-way data bindings, but is suffers from the same side effect. If you pass x as a property to <MyComponent x={x}/> the component will not render an update if x.y changes.

Angular suffers from this problem as well.

Programmers solve this problem by cloning objects so that the data bindings would see the mutation as changing, and render the changes to the DOM.

For example;

let x = {y:1};
let next = {...x, y:2};
console.log(x === next); // prints false

Instead of directly mutating x.y = 2 the above code creates a new object with the new value, and the comparison between x and the next value yields false which tells most frameworks like Angular or React that the data has changed, and anything that needs it should be updated.

So what does this have to do with Redux?

Redux is a reducer framework. It's based upon the idea that it uses reducer functions to perform the above work. The mutation of {...x, y:2} is really simple, but Redux provides tools for the developer to do that in easier to manage ways. For example, selectors and actions are ways of abstracting the work that produces a new mutated value.

If JavaScript could see that x !== x when x.y is changed, then we wouldn't need Redux.

Upvotes: 9

Tarun1704
Tarun1704

Reputation: 191

There are certainly many ways of achieving the same thing, which Redux is trying to solve. Redux is not actually meant for two way data binding. It was developed to maintain state of the whole application.

Redux motivation page is probably the right place to know why Redux.

On my perspective, using Redux/Ngrx has following pros

  • Code is more maintainable.
  • Predictable code (which is easy to debug, provided the devtools from Redux).
  • The state is immutable, and is updated from only one place (reducers).

Upvotes: 4

Related Questions