Ngrx 및 Angular 2 자습서: 반응형 애플리케이션 빌드
게시 됨: 2022-03-11우리는 Angular 영역에서 반응형 프로그래밍에 대해 많이 이야기합니다. 반응형 프로그래밍과 Angular 2는 함께 가는 것 같습니다. 그러나 두 기술 모두에 익숙하지 않은 사람에게는 그것이 무엇에 관한 것인지 파악하는 것이 상당히 어려운 작업일 수 있습니다.
이 기사에서는 Ngrx를 사용하여 반응형 Angular 2 애플리케이션을 빌드함으로써 패턴이 무엇인지, 패턴이 어디에 유용할 수 있는지, 패턴을 사용하여 더 나은 Angular 2 애플리케이션을 빌드하는 방법을 배우게 됩니다.
Ngrx는 반응 확장을 위한 Angular 라이브러리 그룹입니다. Ngrx/Store는 Angular 2의 잘 알려진 RxJS 옵저버블을 사용하여 Redux 패턴을 구현합니다. 애플리케이션 상태를 일반 객체로 단순화하고 단방향 데이터 흐름을 시행하는 등 여러 이점을 제공합니다. Ngrx/Effects 라이브러리를 사용하면 애플리케이션이 부작용을 유발하여 외부 세계와 통신할 수 있습니다.
반응형 프로그래밍이란?
반응형 프로그래밍은 요즘 많이 듣는 용어인데, 과연 어떤 의미일까요?
반응형 프로그래밍은 응용 프로그램이 응용 프로그램에서 이벤트와 데이터 흐름을 처리하는 방법입니다. 반응형 프로그래밍에서는 변경 사항을 요청하는 대신 이러한 변경 사항에 반응하기 위해 구성 요소와 소프트웨어의 다른 부분을 설계합니다. 이것은 큰 변화가 될 수 있습니다.
반응형 프로그래밍을 위한 훌륭한 도구는 아시다시피 RxJS입니다.
들어오는 데이터를 변환하기 위해 관찰 가능 항목과 많은 연산자를 제공함으로써 이 라이브러리는 애플리케이션에서 이벤트를 처리하는 데 도움이 될 것입니다. 사실 옵저버블을 사용하면 이벤트를 일회성 이벤트가 아닌 이벤트의 스트림으로 볼 수 있습니다. 이를 통해 예를 들어 청취할 새 이벤트를 생성하기 위해 이들을 결합할 수 있습니다.
반응형 프로그래밍은 응용 프로그램의 다른 부분 간에 통신하는 방식의 변화입니다. 반응형 프로그래밍에서는 데이터를 필요로 하는 구성 요소나 서비스에 직접 데이터를 푸시하는 대신 데이터 변경 사항에 반응하는 구성 요소 또는 서비스입니다.
Ngrx에 대한 한마디
이 튜토리얼을 통해 구축할 애플리케이션을 이해하려면 핵심 Redux 개념을 빠르게 살펴봐야 합니다.
가게
저장소는 클라이언트 측 데이터베이스로 볼 수 있지만 더 중요한 것은 응용 프로그램의 상태를 반영한다는 것입니다. 당신은 그것을 진실의 단일 소스로 볼 수 있습니다.
Redux 패턴을 따르고 이에 대한 조치를 전달하여 수정할 때 변경하는 유일한 것입니다.
감속기
리듀서는 주어진 작업과 앱의 이전 상태로 무엇을 해야 하는지 아는 기능입니다.
리듀서는 스토어에서 이전 상태를 가져와 순수 기능을 적용합니다. 순수는 함수가 동일한 입력에 대해 항상 동일한 값을 반환하고 부작용이 없음을 의미합니다. 그 순수 함수의 결과로부터 당신은 당신의 상점에 들어갈 새로운 상태를 갖게 될 것입니다.
행위
조치는 상점을 변경하는 데 필요한 정보가 포함된 페이로드입니다. 기본적으로 액션에는 리듀서 함수가 상태를 변경하기 위해 취할 유형과 페이로드가 있습니다.
디스패처
Dispatcher는 단순히 작업을 전달하기 위한 진입점입니다. Ngrx에는 상점에 직접 디스패치 메소드가 있습니다.
미들웨어
미들웨어는 이 문서에서 사용하지 않더라도 부작용을 만들기 위해 전달되는 각 작업을 가로채는 몇 가지 기능입니다. Ngrx/Effect 라이브러리에서 구현되며 실제 응용 프로그램을 구축하는 동안 필요할 가능성이 큽니다.
Ngrx를 사용하는 이유
복잡성
저장소 및 단방향 데이터 흐름은 응용 프로그램 부분 간의 결합을 크게 줄입니다. 이러한 감소된 결합은 각 부분이 특정 상태에만 관심을 갖기 때문에 애플리케이션의 복잡성을 줄입니다.
압형
응용 프로그램의 전체 상태가 한 곳에 저장되므로 응용 프로그램 상태를 전체적으로 쉽게 볼 수 있고 개발 중에 도움이 됩니다. 또한 Redux에는 저장소를 활용하고 애플리케이션의 특정 상태를 재현하거나 시간 여행을 하는 데 도움이 될 수 있는 많은 훌륭한 개발 도구가 제공됩니다.
건축적 단순성
Ngrx의 많은 이점은 다른 솔루션으로 달성할 수 있습니다. 결국 Redux는 아키텍처 패턴입니다. 그러나 협업 편집 도구와 같이 Redux 패턴에 매우 적합한 애플리케이션을 빌드해야 하는 경우 패턴을 따라 쉽게 기능을 추가할 수 있습니다.
수행 중인 작업에 대해 생각할 필요는 없지만 전달되는 모든 작업을 추적할 수 있으므로 모든 애플리케이션을 통해 분석과 같은 몇 가지를 추가하는 것은 간단합니다.
작은 학습 곡선
이 패턴은 널리 채택되고 간단하기 때문에 팀의 새로운 사람들이 당신이 한 일을 빠르게 따라잡기가 정말 쉽습니다.
Ngrx는 모니터링 대시보드와 같이 애플리케이션을 수정할 수 있는 외부 행위자가 많을 때 가장 빛을 발합니다. 이러한 경우 애플리케이션에 푸시되는 모든 수신 데이터를 관리하기 어렵고 상태 관리가 어려워집니다. 이것이 불변 상태로 단순화하려는 이유이며 Ngrx 저장소가 제공하는 것 중 하나입니다.
Ngrx로 애플리케이션 빌드
Ngrx의 힘은 실시간으로 애플리케이션에 푸시되는 외부 데이터가 있을 때 가장 빛납니다. 이를 염두에 두고 온라인 프리랜서를 표시하고 필터링할 수 있는 간단한 프리랜서 그리드를 구축해 보겠습니다.
프로젝트 설정
Angular CLI는 설정 프로세스를 크게 단순화하는 멋진 도구입니다. 사용하지 않을 수도 있지만 이 기사의 나머지 부분에서 사용할 것임을 기억하십시오.
npm install -g @angular/cli
다음으로 새 응용 프로그램을 만들고 모든 Ngrx 라이브러리를 설치하려고 합니다.
ng new toptal-freelancers npm install ngrx --save
프리랜서 감속기
리듀서는 Redux 아키텍처의 핵심 부분이므로 애플리케이션을 빌드하는 동안 먼저 리듀스를 시작하지 않겠습니까?
먼저, 액션이 스토어에 디스패치될 때마다 우리의 새로운 상태를 생성할 책임이 있는 "프리랜서" 리듀서를 생성합니다.
freelancer-grid/freelancers.reducer.ts
import { Action } from '@ngrx/store'; export interface AppState { freelancers : Array<IFreelancer> } export interface IFreelancer { name: string, email: string, thumbnail: string } export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED', } export function freelancersReducer( state: Array<IFreelancer> = [], action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); default: return state; } }
여기 프리랜서 리듀서가 있습니다.
이 함수는 상점을 통해 작업이 전달될 때마다 호출됩니다. 작업이 FREELANCERS_LOADED
이면 작업 페이로드에서 새 배열을 생성합니다. 그렇지 않은 경우 이전 상태 참조를 반환하고 아무것도 추가되지 않습니다.
여기에서 이전 상태 참조가 반환되면 상태가 변경되지 않은 것으로 간주된다는 점에 유의하는 것이 중요합니다. 즉, state.push(something)
을 호출하면 상태가 변경된 것으로 간주되지 않습니다. 감속기 기능을 수행하는 동안 염두에 두십시오.
상태는 변경할 수 없습니다. 상태가 변경될 때마다 새 상태를 반환해야 합니다.
프리랜서 그리드 구성 요소
온라인 프리랜서를 보여줄 그리드 구성 요소를 만드십시오. 처음에는 상점에 있는 것만 반영합니다.
ng generate component freelancer-grid
freelancer-grid.component.ts 에 다음을 입력합니다.
import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState, IFreelancer, ACTIONS } from './freelancer-reducer'; import * as Rx from 'RxJS'; @Component({ selector: 'app-freelancer-grid', templateUrl: './freelancer-grid.component.html', styleUrls: ['./freelancer-grid.component.scss'], }) export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable<Array<IFreelancer>>; constructor(private store: Store<AppState>) { this.freelancers = store.select('freelancers'); } }
그리고 freelancer-grid.component.html 의 다음과 같습니다.
<span class="count">Number of freelancers online: {{(freelancers | async).length}}</span> <div class="freelancer fade thumbail" *ngFor="let freelancer of freelancers | async"> <button type="button" class="close" aria-label="Close" (click)="delete(freelancer)"><span aria-hidden="true">×</span></button><br> <img class="img-circle center-block" src="{{freelancer.thumbnail}}" /><br> <div class="info"><span><strong>Name: </strong>{{freelancer.name}}</span> <span><strong>Email: </strong>{{freelancer.email}}</span></div> <a class="btn btn-default">Hire {{freelancer.name}}</a> </div>
그래서 방금 무엇을 했습니까?
먼저 freelancer-grid
라는 새 구성 요소를 만들었습니다.
구성 요소에는 Ngrx 저장소에 포함된 응용 프로그램 상태의 일부인 freelancers
라는 속성이 포함되어 있습니다. 선택 연산자를 사용하여 전체 응용 프로그램 상태의 freelancers
속성에서만 알림을 받도록 선택합니다. 이제 애플리케이션 상태의 freelancers
속성이 변경될 때마다 관찰 대상에 알림이 전송됩니다.
이 솔루션의 한 가지 아름다운 점은 구성 요소에 종속성이 하나만 있고 구성 요소를 훨씬 덜 복잡하고 쉽게 재사용할 수 있도록 만드는 저장소가 있다는 것입니다.
템플릿 부분에서는 너무 복잡한 작업을 수행하지 않았습니다. *ngFor
에서 비동기 파이프의 사용에 주목하십시오. freelancers
옵저버블은 직접 반복할 수 없지만 Angular 덕분에 비동기 파이프를 사용하여 dom을 래핑 해제하고 해당 값에 바인딩할 수 있는 도구가 있습니다. 이것은 옵저버블 작업을 훨씬 쉽게 만듭니다.
프리랜서 제거 기능 추가
이제 기능 기반이 생겼으므로 애플리케이션에 몇 가지 작업을 추가해 보겠습니다.
주에서 프리랜서를 제거할 수 있기를 원합니다. Redux가 작동하는 방식에 따라 먼저 영향을 받는 각 상태에서 해당 작업을 정의해야 합니다.
이 경우 freelancers
리듀서일 뿐입니다.
export const ACTIONS = { FREELANCERS_LOADED: 'FREELANCERS_LOADED', DELETE_FREELANCER: 'DELETE_FREELANCER', } export function freelancersReducer( state: Array<IFreelancer> = [], action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); case ACTIONS.DELETE_FREELANCER: // Remove the element from the array state.splice(state.indexOf(action.payload), 1); // We need to create another reference return Array.prototype.concat(state); default: return state; } }
새로운 불변 상태를 갖기 위해 이전 배열에서 새 배열을 만드는 것이 여기에서 정말 중요합니다.
이제 이 작업을 상점에 전달할 구성 요소에 freelancers 삭제 기능을 추가할 수 있습니다.
delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) }
간단해 보이지 않나요?
이제 상태에서 특정 프리랜서를 제거할 수 있으며 해당 변경 사항은 애플리케이션을 통해 전파됩니다.
이제 응용 프로그램에 다른 구성 요소를 추가하여 저장소를 통해 서로 상호 작용할 수 있는 방법을 확인하면 어떻게 될까요?
필터 감속기
항상 그렇듯이 감속기부터 시작하겠습니다. 해당 구성 요소의 경우 매우 간단합니다. 리듀서가 항상 우리가 전달한 속성만으로 새로운 상태를 반환하기를 원합니다. 다음과 같이 표시되어야 합니다.
import { Action } from '@ngrx/store'; export interface IFilter { name: string, email: string, } export const ACTIONS = { UPDATE_FITLER: 'UPDATE_FITLER', CLEAR_FITLER: 'CLEAR_FITLER', } const initialState = { name: '', email: '' }; export function filterReducer( state: IFilter = initialState, action: Action): IFilter { switch (action.type) { case ACTIONS.UPDATE_FITLER: // Create a new state from payload return Object.assign({}, action.payload); case ACTIONS.CLEAR_FITLER: // Create a new state from initial state return Object.assign({}, initialState); default: return state; } }
필터 구성 요소
import { Component, OnInit } from '@angular/core'; import { IFilter, ACTIONS as FilterACTIONS } from './filter-reducer'; import { Store } from '@ngrx/store'; import { FormGroup, FormControl } from '@angular/forms'; import * as Rx from 'RxJS'; @Component({ selector: 'app-filter', template: '<form class="filter">'+ '<label>Name</label>'+ '<input type="text" [formControl]="name" name="name"/>'+ '<label>Email</label>'+ '<input type="text" [formControl]="email" name="email"/>'+ '<a (click)="clearFilter()" class="btn btn-default">Clear Filter</a>'+ '</form>', styleUrls: ['./filter.component.scss'], }) export class FilterComponent implements OnInit { public name = new FormControl(); public email = new FormControl(); constructor(private store: Store<any>) { store.select('filter').subscribe((filter: IFilter) => { this.name.setValue(filter.name); this.email.setValue(filter.email); }) Rx.Observable.merge(this.name.valueChanges, this.email.valueChanges).debounceTime(1000).subscribe(() => this.filter()); } ngOnInit() { } filter() { this.store.dispatch({ type: FilterACTIONS.UPDATE_FITLER, payload: { name: this.name.value, email: this.email.value, } }); } clearFilter() { this.store.dispatch({ type: FilterACTIONS.CLEAR_FITLER, }) } }
먼저 우리의 상태를 반영하는 두 개의 필드(이름 및 이메일)가 있는 양식을 포함하는 간단한 템플릿을 만들었습니다.

freelancers
상태에서 수행한 것과는 상당히 다른 방식으로 해당 필드를 상태와 동기화합니다. 실제로 보았듯이 필터 상태를 구독하고 매번 formControl
에 새 값을 할당하도록 트리거합니다.
Angular 2의 좋은 점 중 하나는 옵저버블과 상호 작용할 수 있는 많은 도구를 제공한다는 것입니다.
이전에 비동기 파이프를 보았고 이제 입력 값에 대해 관찰할 수 있는 formControl
클래스를 볼 수 있습니다. 이를 통해 필터 구성 요소에서 수행한 작업과 같은 멋진 작업을 수행할 수 있습니다.
보시다시피 Rx.observable.merge
formControls
제공한 두 개의 옵저버블을 결합한 다음 filter
기능을 트리거하기 전에 새 옵저버블을 디바운스합니다.
간단히 말해서 이름이나 이메일 formControl
이 변경된 후 1초를 기다린 다음 filter
함수를 호출합니다.
굉장하지 않아?
이 모든 것이 몇 줄의 코드로 수행됩니다. 이것이 RxJS를 사랑하는 이유 중 하나입니다. 그렇지 않으면 더 복잡했을 많은 멋진 일을 쉽게 할 수 있습니다.
이제 해당 필터 기능을 살펴보겠습니다. 그것은 무엇을합니까?
단순히 이름과 이메일 값으로 UPDATE_FILTER
작업을 전달하면 리듀서가 해당 정보로 상태 변경을 처리합니다.
좀 더 흥미로운 것으로 넘어가자.
해당 필터가 이전에 만든 프리랜서 그리드와 상호 작용하도록 하려면 어떻게 합니까?
단순한. 스토어의 필터 부분만 들어야 합니다. 코드가 어떻게 생겼는지 봅시다.
import { Component, OnInit } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState, IFreelancer, ACTIONS } from './freelancer-reducer'; import { IFilter, ACTIONS as FilterACTIONS } from './../filter/filter-reducer'; import * as Rx from 'RxJS'; @Component({ selector: 'app-freelancer-grid', templateUrl: './freelancer-grid.component', styleUrls: ['./freelancer-grid.component.scss'], }) export class FreelancerGridComponent implements OnInit { public freelancers: Rx.Observable<Array<IFreelancer>>; public filter: Rx.Observable<IFilter>; constructor(private store: Store<AppState>) { this.freelancers = Rx.Observable.combineLatest(store.select('freelancers'), store.select('filter'), this.applyFilter); } applyFilter(freelancers: Array<IFreelancer>, filter: IFilter): Array<IFreelancer> { return freelancers .filter(x => !filter.name || x.name.toLowerCase().indexOf(filter.name.toLowerCase()) !== -1) .filter(x => !filter.email || x.email.toLowerCase().indexOf(filter.email.toLowerCase()) !== -1) } ngOnInit() { } delete(freelancer) { this.store.dispatch({ type: ACTIONS.DELETE_FREELANCER, payload: freelancer, }) } }
그것보다 더 복잡하지 않습니다.
다시 한 번 RxJS의 기능을 사용하여 필터와 프리랜서 상태를 결합했습니다.
사실, 두 옵저버블 중 하나가 실행된 다음 applyFilter
함수를 사용하여 각 상태를 결합하면 combineLatest
가 실행됩니다. 그렇게 하는 새로운 옵저버블을 반환합니다. 다른 코드 줄을 변경할 필요가 없습니다.
구성 요소가 필터를 얻거나 수정하거나 저장하는 방법에 대해 신경 쓰지 않는 방법에 주목하십시오. 다른 상태에서와 마찬가지로 수신만 합니다. 필터 기능을 추가했을 뿐이고 새로운 종속성은 추가하지 않았습니다.
빛나게 하기
Ngrx를 사용하면 실시간 데이터를 처리해야 할 때 정말 빛을 발한다는 사실을 기억하십니까? 그 부분을 우리 애플리케이션에 추가하고 어떻게 되는지 봅시다.
freelancers-service
소개합니다.
ng generate service freelancer
프리랜서 서비스는 데이터에 대한 실시간 작업을 시뮬레이션하며 다음과 같아야 합니다.
import { Injectable } from '@angular/core'; import { Store } from '@ngrx/store'; import { AppState, IFreelancer, ACTIONS } from './freelancer-grid/freelancer-reducer'; import { Http, Response } from '@angular/http'; @Injectable() export class RealtimeFreelancersService { private USER_API_URL = 'https://randomuser.me/api/?results=' constructor(private store: Store<AppState>, private http: Http) { } private toFreelancer(value: any) { return { name: value.name.first + ' ' + value.name.last, email: value.email, thumbail: value.picture.large, } } private random(y) { return Math.floor(Math.random() * y); } public run() { this.http.get(`${this.USER_API_URL}51`).subscribe((response) => { this.store.dispatch({ type: ACTIONS.FREELANCERS_LOADED, payload: response.json().results.map(this.toFreelancer) }) }) setInterval(() => { this.store.select('freelancers').first().subscribe((freelancers: Array<IFreelancer>) => { let getDeletedIndex = () => { return this.random(freelancers.length - 1) } this.http.get(`${this.USER_API_URL}${this.random(10)}`).subscribe((response) => { this.store.dispatch({ type: ACTIONS.INCOMMING_DATA, payload: { ADD: response.json().results.map(this.toFreelancer), DELETE: new Array(this.random(6)).fill(0).map(() => getDeletedIndex()), } }); this.addFadeClassToNewElements(); }); }); }, 10000); } private addFadeClassToNewElements() { let elements = window.document.getElementsByClassName('freelancer'); for (let i = 0; i < elements.length; i++) { if (elements.item(i).className.indexOf('fade') === -1) { elements.item(i).classList.add('fade'); } } } }
이 서비스는 완벽하지 않지만 기능을 수행하며 데모 목적으로 몇 가지를 시연할 수 있습니다.
첫째, 이 서비스는 매우 간단합니다. 사용자 API를 쿼리하고 결과를 저장소에 푸시합니다. 생각보다 간단하며 데이터가 어디로 가는지 생각할 필요가 없습니다. Redux를 매우 유용하면서도 동시에 위험하게 만드는 저장소로 이동합니다. 하지만 나중에 다시 다루겠습니다. 10초마다 서비스는 몇 명의 프리랜서를 선택하고 삭제하는 작업을 다른 몇 명의 프리랜서에게 작업과 함께 보냅니다.
감속기가 이를 처리할 수 있도록 하려면 다음과 같이 수정해야 합니다.
import { Action } from '@ngrx/store'; export interface AppState { freelancers : Array<IFreelancer> } export interface IFreelancer { name: string, email: string, } export const ACTIONS = { LOAD_FREELANCERS: 'LOAD_FREELANCERS', INCOMMING_DATA: 'INCOMMING_DATA', DELETE_FREELANCER: 'DELETE_FREELANCER', } export function freelancersReducer( state: Array<IFreelancer> = [], action: Action): Array<IFreelancer> { switch (action.type) { case ACTIONS.INCOMMING_DATA: action.payload.DELETE.forEach((index) => { state.splice(state.indexOf(action.payload), 1); }) return Array.prototype.concat(action.payload.ADD, state); case ACTIONS.FREELANCERS_LOADED: // Return the new state with the payload as freelancers list return Array.prototype.concat(action.payload); case ACTIONS.DELETE_FREELANCER: // Remove the element from the array state.splice(state.indexOf(action.payload), 1); // We need to create another reference return Array.prototype.concat(state); default: return state; } }
이제 우리는 그러한 작업을 처리할 수 있습니다.
그 서비스에서 시연되는 한 가지는 상태 변경의 모든 프로세스가 동기적으로 수행된다는 점에서 이를 알아차리는 것이 매우 중요하다는 것입니다. 상태의 응용 프로그램이 비동기인 경우 this.addFadeClassToNewElements();
이 함수가 호출될 때 DOM 요소가 생성되지 않으므로 작동하지 않습니다.
개인적으로 예측 가능성을 향상시키기 때문에 매우 유용하다고 생각합니다.
반응형 방식으로 애플리케이션 구축
이 튜토리얼을 통해 Ngrx, RxJS 및 Angular 2를 사용하여 반응형 애플리케이션을 구축했습니다.
이미 보았듯이 이들은 강력한 도구입니다. 여기에서 구축한 것은 Redux 아키텍처의 구현으로도 볼 수 있으며 Redux는 그 자체로 강력합니다. 그러나 여기에도 몇 가지 제약이 있습니다. Ngrx를 사용하는 동안 이러한 제약 조건은 필연적으로 우리가 사용하는 애플리케이션 부분에 반영됩니다.
위의 다이어그램은 방금 수행한 아키텍처의 대략적인 것입니다.
일부 구성 요소가 서로 영향을 미치더라도 서로 독립적임을 알 수 있습니다. 이것이 이 아키텍처의 특징입니다. 구성 요소는 저장소라는 공통 종속성을 공유합니다.
이 아키텍처의 또 다른 특별한 점은 함수를 호출하지 않고 작업을 발송한다는 것입니다. Ngrx의 대안은 애플리케이션의 관찰 가능 항목으로 특정 상태를 관리하는 서비스만 만들고 작업 대신 해당 서비스에 대한 함수를 호출하는 것입니다. 이러한 방식으로 문제가 있는 상태를 격리하면서 상태의 중앙 집중화 및 반응성을 얻을 수 있습니다. 이 접근 방식은 감속기를 생성하는 오버헤드를 줄이고 작업을 일반 객체로 설명하는 데 도움이 될 수 있습니다.
애플리케이션의 상태가 다른 소스에서 업데이트되고 있고 엉망이 되기 시작하면 Ngrx가 필요한 것입니다.