Нужно ли нам управлять состоянием в каждом приложении Angular? Может быть, не всегда, так как же элегантно реализовать управление состоянием для приложений, в которых оно нам действительно нужно? NgRx - одна из библиотек, используемых для управления состоянием приложения. Это реализация Redux для Angular.

Сначала давайте посмотрим на проблему, которую мы пытаемся решить, а затем разберемся с концепциями, лежащими в основе NgRx, и, наконец, перейдем к кодированию.

Хотите прочитать эту историю позже? Сохраните в Журнале.

Состояние приложения

Что такое состояние приложения? Теоретически это вся память приложения, но, как правило, это данные, полученные через вызовы API, пользовательские вводы, состояние пользовательского интерфейса презентации, настройки приложения и т. Д. Проще говоря, это данные, которые могут различать два экземпляра то же приложение. Простым конкретным примером состояния приложения может быть список клиентов, поддерживаемый в приложении.

Проблема, которую мы пытаемся решить

Для простоты предположим, что у нас есть список клиентов в приложении, и это состояние, которым мы пытаемся управлять. Некоторые вызовы API и вводимые пользователем данные могут изменять состояние (т.е. список), добавляя или удаляя клиентов. Изменение состояния должно быть отражено в пользовательском интерфейсе и других зависимых компонентах. Конечно, в этом конкретном случае мы можем иметь глобальную переменную для хранения списка, а затем добавлять / удалять клиентов из / в него, а затем писать код для обновления пользовательского интерфейса и зависимостей. Но в этом дизайне есть много подводных камней, которые не являются предметом рассмотрения в этой статье. NgRx - отличный дизайн для большинства требований государственного управления. В самом деле, он немного шаблонен по сравнению с другими конкурентами, такими как NGXS, Akita или простой RxJS.

Управление состоянием приложения NgRx

Давайте посмотрим на реализацию NgRx - нужно понять несколько компонентов.

  • Магазин. Магазин - это то, что хранит состояние приложения.
  • Действие: уникальное событие, отправляемое компонентами и службами, которое описывает, как следует изменить состояние. Например, «Добавить клиента» может быть действием, которое изменит состояние (т. Е. Добавит нового клиента в список).
  • Редуктор: все изменения состояния происходят внутри редуктора; он реагирует на действие и на основе этого действия создает новое неизменяемое состояние и возвращает его в хранилище.
  • Селектор: Селектор - это функция, используемая для получения части состояния из магазина.
  • Эффект: механизм, который отслеживает отправленные действия в наблюдаемом потоке, обрабатывает ответ сервера и немедленно или асинхронно возвращает редуктору новые действия для изменения состояния. Обратите внимание, что мы не используем эффект в этом примере приложения.

Это взаимодействие между этими компонентами в NgRx.

Пользовательский интерфейс и другие компоненты отправляют действия. Действия могут иметь полезную нагрузку, которая должна изменять состояние. Редуктор создает новое состояние, описанное указанным действием, и возвращает его в хранилище. Как только магазин обновится с новым состоянием, он уведомит пользовательский интерфейс и все зависимые компоненты. Каждый пользовательский интерфейс реагирует на изменение состояния, и его представление обновляется, чтобы отразить изменения.

Это представление нашего примера состояния, списка клиентов.

Элемент управления пользовательского интерфейса «Добавить нового клиента» отправляет действие AddCustomer с новым клиентом в качестве полезной нагрузки в этом действии. Редуктор выполняет действие AddCustomer, а новый клиент приходит в качестве полезной нагрузки и создает новый список с существующими клиентами. Затем он обновит магазин, добавив в него новый список клиентов. Наконец, он уведомит пользовательский интерфейс и отобразит новый список.

Наконец, кодирование

Предпосылки:

Убедитесь, что установлены Node.js и Angular CLI. Вы можете запустить ng --version, чтобы узнать, какие версии установлены на вашем компьютере.

1. Создайте приложение Angular с помощью Angular CLI.

ng new angular-state-management

Выберите «Нет» и «CSS».

? Would you like to add Angular routing? No
? Which stylesheet format would you like to use? CSS

Он создаст все необходимые файлы и установит зависимости. Это займет несколько минут.

2. Загрузите проект в IDE (я использую IntelliJ IDEA).

3. Запустите приложение.

Давайте запустим приложение, созданное с помощью интерфейса командной строки, чтобы убедиться, что все создано правильно.

npm start

Убедитесь, что приложение работает на http: // localhost: 4200 /

4. Установите NgRx.

Давайте установим NgRx сейчас (вы можете использовать новое окно терминала или выйти из того, в котором вы находитесь, нажав клавиши ctrl + C)

npm install @ngrx/store --save

Кроме того, вы можете запустить ng add @ngrx/store, если у вас версия CLI 6+.

Обратите внимание, что @ngrx/store был добавлен в файл package.json.

5. Создайте модель клиента.

Теперь мы начинаем добавлять код. Во-первых, давайте создадим customer файл с помощью интерфейса командной строки.

ng g class models/customer

Как еще один вариант, вы можете добавить его с помощью редактора.

Файл customer.ts был создан в папке src \ app \ models. Добавьте к нему свойство name.

export class Customer {
   public name: String = '';
}

6. Добавить действия

Теперь мы собираемся добавить код, связанный с NgRx. Как показывает наша диаграмма выше, состояние, которым мы собираемся управлять, - это набор клиентов. Мы можем изменить сбор данных о состоянии клиента с помощью действий. В этом конкретном случае у нас есть два действия, которые могут изменить состояние:

  • AddCustomer
  • RemoveCustomer

Создайте файл TypeScript customer.actions.ts в папке src / app для действий клиентов с помощью редактора.

Добавьте следующий код в файл customer.actions.ts:

import {Action} from '@ngrx/store';
export enum CustomerActionTypes {
     Add = '[Customer Component] Add',
     Remove = '[Customer Component] Remove'
}
export class ActionEx implements Action {
     readonly type;
     payload: any;
}
export class CustomerAdd implements ActionEx {
     readonly type = CustomerActionTypes.Add;
     constructor(public payload: any) {
     }
}
export class CustomerRemove implements ActionEx {
     readonly type = CustomerActionTypes.Remove;
     constructor(public payload: any) {
     }
}

7. Добавьте клиентского редуктора

Добавим редуктор; все изменения состояния происходят внутри редуктора на основе выбранного «действия». Если состояние изменяется, редуктор создаст нового клиента, а не изменяет существующий список клиентов. В случае изменения состояния редуктор всегда будет возвращать вновь созданный объект списка клиентов.

Создайте файл TypeScript customer.reducer.ts в папке src / app для CustomerReducer с помощью редактора.

import {ActionEx, CustomerActionTypes} from './customer.actions';
export const initialState = [];
export function CustomerReducer(state = initialState, action: ActionEx) {
   switch (action.type) {
      case CustomerActionTypes.Add:
          return [...state, action.payload];
      case CustomerActionTypes.Remove:
          return [
             ...state.slice(0, action.payload),
             ...state.slice(action.payload + 1)
          ];
      default:
         return state;
  }
}

8. Добавьте магазин NgRx в приложение.

Давайте добавим в приложение модуль магазина.

Добавьте импорт в app.module.ts:

import { StoreModule } from '@ngrx/store';
import { CustomerReducer } from './customer.reducer';

И модуль магазина

StoreModule.forRoot({ customers: CustomerReducer })

Теперь app.module.ts должен выглядеть так.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { StoreModule } from '@ngrx/store';
import { CustomerReducer } from './customer.reducer';
@NgModule({
   declarations: [
      AppComponent
   ],
   imports: [
      BrowserModule,
      StoreModule.forRoot({ customers: CustomerReducer })
   ],
   providers: [],
   bootstrap: [AppComponent]
})
export class AppModule { }

9. Добавьте компонент пользовательского интерфейса для просмотра клиентов.

ng g c  CustomersView

Добавьте код в файл customers-view.compoment.ts .

Объявите клиентов, которые наблюдаются на вершине класса

customers: Observable<Customer[]>;

И изменим конструктор:

constructor(private store: Store<{ customers: Customer[] }>) {
   this.customers = store.pipe(select('customers'));
}

Импортируйте необходимые зависимости вверху:

import {Customer} from '../models/customer';
import {select, Store} from '@ngrx/store';
import {Observable} from 'rxjs';

Теперь файл customers-view.compoment.ts должен выглядеть так:

import {Component} from '@angular/core';
import {Customer} from '../models/customer';
import {Observable} from 'rxjs';
import {select, Store} from '@ngrx/store';
@Component({
    selector: 'app-customers-view',
    templateUrl: './customers-view.component.html',
    styleUrls: ['./customers-view.component.css']
})
export class CustomersViewComponent {
    customers: Observable<Customer[]>;
    constructor(private store: Store<{ customers: Customer[] }>) {
        this.customers = store.pipe(select('customers'));
    }
}

Добавьте следующий код в файл customers-view.compoment.html,

<h4>List of Customers</h4>
<ul class="customers">
    <li *ngFor="let customer of customers | async; let i=index">
        <span >{{i+1}}.</span> {{customer.name}}
    </li>
</ul>

И чтобы сделать список более приятным, добавьте код CSS в файл customers-view.compoment.css.

.customers {
    margin: 0 0 2em 0;
    list-style-type: none;
    padding: 0;
    width: 15em;
}
.customers li {
    background-color: steelblue;
    color: white;
    border-radius: 4px;
    padding: 4px;
    margin: 2px;
}

10. Добавьте элементы управления пользовательского интерфейса, чтобы добавить новых клиентов.

ng g c  CustomerAdd

customer-add.component.ts

import {Component} from '@angular/core';
import {select, Store} from '@ngrx/store';
import {Customer} from '../models/customer';
import {Observable} from 'rxjs';
import {CustomerAdd} from '../customer.actions';
@Component({
    selector: 'app-customer-add',
    templateUrl: './customer-add.component.html',
    styleUrls: ['./customer-add.component.css']
})
export class CustomerAddComponent {
    customers: Observable<Customer[]>;
    constructor(private store: Store<{ customers: Customer[] }>) {
         this.customers = store.pipe(select('customers'));
    }
    AddCustomer(customerName: string): void {
        const customer = new Customer();
        customer.name = customerName;
        this.store.dispatch(new CustomerAdd(customer));
    }
}

И файл c ustomer-add.component.html.

<h4>Add New Customer</h4>
<input #box ><button (click)="AddCustomer(box.value)">Add</button>

11. Обновите компонент приложения с помощью компонентов CustomerView и CustomerAdd.

Теперь обновите файл app.component.html, удалив содержимое по умолчанию и вставив компоненты app-customers-view и app-customer-add.

Файл app.component.html должен выглядеть следующим образом:

<div style="text-align:center">
    <h1>
        Welcome to {{ title }}!
    </h1>
</div>
<app-customers-view></app-customers-view>
<app-customer-add></app-customer-add>

12. Запустите приложение.

Наш код готов! Давайте снова запустим приложение (если оно еще не запущено).

npm start

13. Подключите действие «Удалить клиента».

Мы собираемся добавить кнопку справа от ярлыка клиента и подключить отправку действия CutomerRemove.

Добавьте в наш файл customers-view.compoment.ts следующую строку:

removeCustomer(customerIndex): void {
    this.store.dispatch(new CustomerRemove(customerIndex));
}

Теперь файл customers-view.compoment.ts выглядит следующим образом:

import {Component} from '@angular/core';
import {Customer} from '../models/customer';
import {Observable} from 'rxjs';
import {select, Store} from '@ngrx/store';
import {CustomerRemove} from '../customer.actions';
@Component({
    selector: 'app-customers-view',
    templateUrl: './customers-view.component.html',
    styleUrls: ['./customers-view.component.css']
})
export class CustomersViewComponent {
    customers: Observable<Customer[]>;
    constructor(private store: Store<{ customers: Customer[] }>) {
        this.customers = store.pipe(select('customers'));
    }
    removeCustomer(customerIndex) {
        this.store.dispatch(new CustomerRemove(customerIndex));
    }
}

И в файл customers-view.compoment.html добавьте следующее:

<button style="float: right" (click)="removeCustomer(i)">Remove</button>

customers-view.compoment.html теперь выглядит так:

<h4>List of Customers</h4>
<ul class="customers">
    <li *ngFor="let customer of customers | async; let i=index">
        <span >{{i+1}}.</span> {{customer.name}}
        <button style="float: right"     (click)="removeCustomer(i)">Remove</button>
    </li>
</ul>

Вот финальная версия приложения.

Надеюсь, эта статья поможет вам лучше понять NgRx и использовать его с приложением Angular.

Давай аплодируем, если тебе это действительно нравится.

📝 Сохраните эту историю в Журнале.

👩‍💻 Просыпайтесь каждое воскресное утро и слушайте самые интересные истории из области технологий, ожидающие вас в вашем почтовом ящике. Прочтите информационный бюллетень« Примечательно в технологиях .