对于一般的中小angular项目,使用类似ngrx或者mobx等第三方库作为状态管理反而会更麻烦,那么我们就可以使用框架自带的rxjs+service来做一个简单的状态管理。下面讲解一下利用rxjs+service作状态管理实例,基本能够解决大部分问题,如果应用相对复杂,状态和数据量大,还是建议使用ngrx。
首先初始化建立一个项目,建立一个module: state-manage来演示,引入antd库(网上很多教程,此处略过啦哈~)
建立一个service,将其注入到root
// src/app/state-manage/state.service.ts
import { Injectable, EventEmitter} from '@angular/core';
export declare interface HeroDefind {
id: string;
name: string;
age: number;
power: string;
}
@Injectable({
providedIn: 'root'
})
export class StateService {
heroList: HeroDefind[] = [
{
id: '1',
name: 'Iron Man',
age: 32,
power: 'so much money'
},
{
id: '2',
name: 'Spider Man',
age: 18,
power: 'With great power comes great responsibility.'
},
{
id: '3',
name: 'Captain America',
age: 32,
power: 'strong'
},
{
id: '4',
name: 'Black Widow',
age: 32,
power: 'beautify'
}
];
notifyChange$ = new EventEmitter<any>();
constructor() { }
}
conpoment中注入该服务
// src/app/state-manage/index/index.component.ts
import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import {StateService, HeroDefind} from './../state.service';
@Component({
selector: 'app-state-index',
templateUrl: './index.component.html',
styleUrls: ['./index.component.less']
})
export class IndexComponent implements OnInit {
heros: HeroDefind[] = [];
constructor(
private stateService: StateService,
private route: Router
) {
this.heros = this.stateService.heroList;
}
ngOnInit() {
}
goEditor(hero: HeroDefind) {
this.route.navigateByUrl(`/s/edit?id=${hero.id}`);
}
godetail(hero: HeroDefind) {
this.route.navigateByUrl(`/s/detail?id=${hero.id}`);
}
}
模板渲染
// src/app/state-manage/index/index.component.html
<h3 [ngStyle]="{ margin: '16px 0' }">英雄列表</h3>
<nz-table #basicTable [nzData]="heros">
<thead>
<tr>
<th>Name</th>
<th>Age</th>
<th>Power</th>
<th>Action</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let data of basicTable.data">
<td>{{ data.name }}</td>
<td>{{ data.age }}</td>
<td>{{ data.power }}</td>
<td>
<a (click)="goEditor(data)">编辑</a>
<nz-divider nzType="vertical"></nz-divider>
<a (click)="godetail(data)">详情</a>
</td>
</tr>
</tbody>
</nz-table>
结果图如下:
好了,下面我们尝试跳转页面,修改英雄数据
// src/app/state-manage/edit/edit.component.ts
import { Component, OnInit } from '@angular/core';
import { FormBuilder, FormGroup } from '@angular/forms';
import { ActivatedRoute } from '@angular/router';
import { StateService, HeroDefind } from '../state.service';
@Component({
selector: 'app-state-edit',
templateUrl: './edit.component.html',
styleUrls: ['./edit.component.less']
})
export class EditComponent implements OnInit {
validateForm: FormGroup;
constructor(
private fb: FormBuilder,
private stateService: StateService,
private route: ActivatedRoute,
) { }
ngOnInit(): void {
const heroId = this.route.snapshot.queryParams.id;
const currentHero = this.stateService.heroList.find((hero: HeroDefind) => hero.id === heroId);
this.validateForm = this.fb.group({
id: [currentHero.id],
name: [currentHero.name],
age: [currentHero.age],
power: [currentHero.power],
});
}
submitForm(value: { anem: string; age: string; power: string, id: string}): void {
// tslint:disable-next-line:forin
for (const key in this.validateForm.controls) {
this.validateForm.controls[key].markAsDirty();
this.validateForm.controls[key].updateValueAndValidity();
}
}
}
// src/app/state-manage/edit/edit.component.html
<form nz-form [formGroup]="validateForm" (ngSubmit)="submitForm(validateForm.value)">
<nz-form-item>
<nz-form-label [nzSpan]="7" nzRequired>Name</nz-form-label>
<nz-form-control [nzSpan]="12" nzHasFeedback nzValidatingTip="Validating...">
<input nz-input formControlName="name"/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7" nzRequired>Age</nz-form-label>
<nz-form-control [nzSpan]="12" nzHasFeedback nzValidatingTip="Validating...">
<input nz-input formControlName="age"/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-label [nzSpan]="7" nzRequired>Power</nz-form-label>
<nz-form-control [nzSpan]="12" nzErrorTip="Please write something here!">
<input nz-input formControlName="power"/>
</nz-form-control>
</nz-form-item>
<nz-form-item>
<nz-form-control [nzOffset]="7" [nzSpan]="12">
<button nz-button nzType="primary" [disabled]="!validateForm.valid">Submit</button>
</nz-form-control>
</nz-form-item>
</form>
现在我们希望在submit的时候,通知列表页更新修改后的数据,如何做到呢?我们只是单纯修改service里的数据,是无法更新列表页的,此时需要rxjs出场了。
回到我们的列表,修改index.component.ts
// src/app/state-manage/index/index.component.ts
ngOnInit() {
this.heros = this.stateService.heroList;
+ this.stateService.notifyChange$.subscribe((hero: HeroDefind) => {
+ this.heros = this.stateService.heroList;
+ });
}
修改edit.conpoment.ts
submitForm(value: { name: string; age: number; power: string, id: string}): void {
// tslint:disable-next-line:forin
for (const key in this.validateForm.controls) {
this.validateForm.controls[key].markAsDirty();
this.validateForm.controls[key].updateValueAndValidity();
}
+ const currentHeroIndex = this.stateService.heroList.findIndex((hero: HeroDefind) => hero.id === value.id);
+ this.stateService.heroList[currentHeroIndex] = value;
+ this.stateService.notifyChange$.emit(value);
+ history.back();
}
现在,我们修改英雄数据
返回列表页,发现数据已经做了修改
即使跳到详情页,也是修改后的数据
这说明,列表页,编辑页,详情页,都是使用同一个service实例,实现数据的共享,我们要做的,就是在数据更新之后,能够通知其他组件更新视图。
但是还有一个问题,就是我们的英雄数据是在服务器拉取的,存在异步问题,那么列表页如何异步从service读取数据呢?其实不用过多修改,在上面的代码之上修改service
import { Injectable, EventEmitter} from '@angular/core';
export declare interface HeroDefind {
id: string;
name: string;
age: number;
power: string;
}
@Injectable({
providedIn: 'root'
})
export class StateService {
heroList: HeroDefind[] = [];
notifyChange$ = new EventEmitter<any>();
constructor() {
setTimeout(() => {
this.heroList = [
{
id: '1',
name: 'Iron Man',
age: 32,
power: 'so much money'
},
{
id: '2',
name: 'Spider Man',
age: 18,
power: 'With great power comes great responsibility.'
},
{
id: '3',
name: 'Captain America',
age: 32,
power: 'strong'
},
{
id: '4',
name: 'Black Widow',
age: 32,
power: 'beautify'
}
];
// 因为列表页index.component.ts已经订阅接受英雄数据的更新发布,
// 此处直接发布订阅就可以更新列表数据啦
+ this.notifyChange$.emit();
}, 3000);
}
}
当然,你可以不做页面缓存,在返回列表页的时候重新拉取服务器数据。
至于rxjs的用法和原理,网上很多教程,也更详细,不做累赘啦哈~