Common Angular conventions

React has Redux, Vue has Vuex. Does Angular or AngularJS have their own state management? Of course you can use Ngrx/Store.

In my opinion, one of the biggest differences between Angular/AngularJS and other frameworks is di-based services. That is, we can basically do this without using any state management tools.

In Angular development, we often follow the following pattern:

  • Service encapsulation API (data capture and update)
  • Component Call Service is responsible for retrieving and modifying data

There are really only two agreements: the API is in the Service and the View logic is in the Component. However, there are no clear rules like React where the data logic layer is located. They tend to stack in the Component. This disadvantage is also obvious: Component reuse becomes difficult, resulting in a lot of redundant code. Of course, another side effect is to make the Component big.

Of course, what we call the front-end framework is now the structure of the MVVM, the Model is the data (the data returned by the Call API in the Service), the View is the HTML that the page displays, and the View Model is the data that drives the page, the Component class. This is different from our usual MVC structure, because controlllers are no longer there, largely because they are being handled by state management tools.

In fact, many projects misunderstand service. Even the project I’m maintaining now uses the Service as a wrapper around an API to call the API in the Component. Instead, simply stack the logic into components. If you look at Angular’s description of services, you’ll see that there are roughly two types of services: data Service and service.

component -> data (service)  -> data-service (api)
Copy the code

Component gets data and performs logical calculations through the Service, which modifies and updates the API through the Call Data Service. That is, Component should not call the API directly to modify data.

The middle layer is one that many Angular projects ignore. In the middle is what we know as state management. A simple data management layer can be easily implemented using Angular’s default RxJS.

Implement a data management layer through services

To differentiate, we can call the data layer as state service and the API layer as data service. The directory structure is similar to the following:

---- user.component.ts
---- user.component.html
---- user.state.service.ts
---- user.data.service.ts
---- user.state.module.ts
Copy the code

Of course, it can be pulled out for user.state.service.ts reuse. Influenced by Flex, we can choose to make a Flex-like one-way data flow:

  • The State service defines the view State structure that can be provided
  • The State service provides a way to modify State, not externally (read only)
  • State service Provides notification of State changes

In fact, you can easily see that this is very close to what we said before about state management. I won’t go into the details, but this is one of the definitions I’m used to:

import { Injectable } from '@angular/core';
import { BehaviorSubject, Observable } from 'rxjs';
import { User } from './type';

export interface UserState {
  users: User[];
}

@Injectable({
  providedIn: 'root'
})
export class UserStateService {
  userState$: Observable<UserState>;

  private _userState$ = new BehaviorSubject<UserState>(null);
  constructor() {
    this.userState$ = this._userState$.asObservable();
  }

  get userState(): UserState {
    return this._userState$.getValue();
  }

  addUser(user: User): void {
    const users = [
      ...this.userState.users,
      user
    ];

    this._userState$.next({
      users
    })
  }
}
Copy the code

Here, of course, are some questions:

  • If there are multiple data updates on the State, it is possible to keep the State as small as possible, and even the update of the entire State is much cheaper than Angular’s dirty check.
  • How to handle asynchrony gracefully
  • Although it is a routine writing method, but often need to define a lot of variables, write repeated logic, is it possible to achieve through a library.

On the first question, how do you split state?

If you’re familiar with NgRx, there’s only something called Selector, which is pretty simple, like:

interface UserState {
  userNames: string[];
  foods: Food[];
}
Copy the code

I just want to care about what happens to users, what happens to Foods and I don’t care about it, but it’s pretty simple:

userState$: Observable<UserState>; / /... food$ = this.userState$.pipe( map(userState => userState.foods), distinctUntilChanges(), ) //.... Select (state: Observable<any>, selector: (state: any) => any): Observable<any> { return state.pipe( map(selector), distinctUntilChanges() ); } // use select: const foods$= this. Select (userState$, userState => userstate.food);Copy the code

This is true because state changes are immutable, meaning that each state update is not a modification of the state itself, but rather a creation of a new state. For example:

// Good to add new item: state = { ... state, foods: [ ...state.foods, new Food(), ] } // Bad to add new item state.foods.push(new Food())Copy the code

If you’re familiar with Redux you should be familiar with the first one, but, frankly, it’s really messy. Of course you can avoid using a bunch of spread operators by using immutable. Js or Immer.

By the way, thanks to Angular’s dirty check, many times data changes that we don’t really need to know about… Of course, I prefer RxJS to be able to see changes in the data as much as possible so that we can reduce our reliance on dirty checks.

The second question is how do you handle asynchrony?

The easiest way to think about it, of course, is you can do whatever you want with it. Just remember, when you update your status, you need to update it manually. This, of course, is rather rough. But, however, this is much more elegant than most call apis handled directly in component.

Mutations and action must be a purely synchronous method, so that you can encapsulate the pure logic in mutations, and when you use asynchronous, Action is used, but the business logic is still call mutations.

We can certainly do something like this, but the difference is, in this case, it’s just a verbal agreement, there’s no way to guarantee it through a framework, there’s no way to tell at a glance what’s synchronous and what’s asynchronous. (Async await is of course one method)

Of course, said so much, in fact, this article is to draw out the content we will introduce next: NgRx Componet Store.