上一篇利用 Angular 路由机制实作待办事项清单与表单页面的切换,这一篇将路由的参数或资料的定义,来实作待办事项的编辑功能。
在实作之前,需要在 task.component.ts 中加入 @Output
属性,并在点选编辑按钮时触发此属性 emit()
方法。
export class TaskComponent implements OnInit, OnChanges {
@Output() edit = new EventEmitter<void>();
}
<div class="card">
<div class="content">
<span>
{{ subject | slice: 0:10 }}<span *ngIf="subject.length > 10">... </span>
<button type="button" [disabled]="state === TaskState.Finish" (click)="edit.emit()">编辑</button>
</span>
</div>
</div>
另外,在 task.service.ts 加入依编号取得待办事项方法。
export class TaskRemoteService {
get(id: number): Observable<Task> {
return this.httpClient.get<Task>(`${this._url}/${id}`);
}
}
在路由定义中,除了指定单纯的字串之外,可以透过冒号 (:) 指定一路由变数,来利用网址路径的内容传递资讯。因此,可以在 app-routing.module.ts 加入待办事项表单的路由设定,并在此设定加入 :id
来接收待办事项编号,
const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: 'main' },
{ path: 'main', component: MainPageComponent },
{ path: 'task-list', component: TaskPageComponent },
{ path: 'task-form', component: TaskFormComponent },
{ path: 'task-form/:id', component: TaskFormComponent },
];
接着就可以在 task-list.component.html 绑定待办事项的编辑事项,并在 task-list.component.ts 中利用 Router 服务切换至表单页面。
<ng-container *ngIf="tasks$ | async as tasks; else dataEmpty">
<app-task
*ngFor="let task of tasks; let odd = odd"
[class.odd]="odd"
[subject]="task.subject"
[(state)]="task.state"
[level]="task.level"
[tags]="task.tags"
[expectDate]="task.expectDate"
[finishedDate]="task.finishedDate"
(edit)="onEdit(task.id)"
></app-task>
</ng-container>
export class TaskListComponent implements OnInit {
constructor(private router: Router, private taskService: TaskRemoteService) {}
onEdit(id: number): void {
this.router.navigate(['task-form', id]);
}
}
Angular 内建的 ActivatedRoute 服务元件可以用来取得从路由中取得变数内容,因此可以在 task-form.component.ts 中注入 ActivatedRoute 服务,并使用此服务的 snapshot
属性来取得 id
路由变数。
export class TaskFormComponent implements OnInit {
constructor(private fb: FormBuilder, private route: ActivatedRoute, private taskService: TaskRemoteService) {}
ngOnInit(): void {
const id = +this.route.snapshot.paramMap.get('id');
if (!!id) {
this.taskService
.get(id)
.pipe(
tap(() => this.tags.clear),
tap((task) => this.onAddTag(task.tags.length))
)
.subscribe((task) => this.form.patchValue(task));
}
}
onAddTag(count: number): void {
for (let i = 0; i <= count - 1; i++) {
const tag = this.fb.control(undefined);
this.tags.push(tag);
}
}
}
在上面程序中,因为取得路由参数会是字串型别,故需要利用 +
来转换成数值型别;另外,在响应式表单 (Reactive Form) 中,需要先存在表单阵列 (FormArray) 的项目结构,才能在利用 patchValue()
方法设定表单值後,页面能够正确的显示。
不过利用 snapshot
所取得路由变数会有所限制;首先在 task-form.component.ts 中的表单模型加入 id
栏位,并注入 Router 服务来实作下笔待办事项页面切换的需求。
export class TaskFormComponent implements OnInit {
get id(): FormControl {
return this.form.get('id') as FormControl;
}
constructor(private fb: FormBuilder, private router: Router, private route: ActivatedRoute, private taskService: TaskRemoteService) {}
ngOnInit(): void {
this.form = this.fb.group({
id: this.fb.control(undefined),
subject: this.fb.control(undefined, [Validators.required], [this.shouldBeUnique.bind(this)]),
state: this.fb.control(0),
level: this.fb.control(undefined, [Validators.required]),
tags: this.fb.array([], [this.arrayCannotEmpty()]),
});
}
onNext(): void {
this.router.navigate(['task-form', this.id.value + 1]);
}
}
<form [formGroup]="form">
<div class="button">
<button type="button" (click)="onSave()">储存</button>
<button type="button" (click)="onNext()">下一笔</button>
</div>
</form>
从上图结果可见,当使用 snapshot
取得路由变数时,因为 OnInit()
生命周期方法只会在元件载入被触发一次,而导致在路由切换後无法正确更换表单资料。此时,则会使用 ActivatedRoute 服务中的 paramMap
属性来监控订阅路由变数的变化。
export class TaskFormComponent implements OnInit {
constructor(private fb: FormBuilder, private router: Router, private route: ActivatedRoute, private taskService: TaskRemoteService) {}
ngOnInit(): void {
this.route.paramMap
.pipe(
map((param) => +param.get('id')),
filter((id) => !!id),
switchMap((id) => this.taskService.get(id)),
tap(() => this.tags.clear()),
tap((task) => this.onAddTag(task.tags.length))
)
.subscribe((task) => this.form.patchValue(task));
}
}
需要注意一点,针对 Observable 物件所建立的订阅监控,在其状态未为完成 (complete) 前是会一直存在的,而在每次元件页面载入时都会建立一个路由的订阅,因此需要在元件被销毁时取消此路由订阅。故在 task-form.component.ts 中实作 OnDestroy
方法,在此取消路由的订阅。
export class TaskFormComponent implements OnInit, OnDestroy {
routerSubscription: Subscription;
ngOnInit(): void {
this.routerSubscription = this.route.paramMap
.pipe(
map((param) => +param.get('id')),
filter((id) => !!id),
switchMap((id) => this.taskService.get(id)),
tap(() => this.tags.clear()),
tap((task) => this.onAddTag(task.tags.length))
)
.subscribe((task) => this.form.patchValue(task));
}
ngOnDestroy(): void {
this.routerSubscription.unsubscribe();
}
}
若需求需要针对多个订阅监控的取消,上面程序会多出不少的 Subscription
属性,此时可以利用 RxJs 的 takeUntil()
运算方法来减化程序。
export class TaskFormComponent implements OnInit, OnDestroy {
stop$ = new Subject<void>();
ngOnInit(): void {
this.route.paramMap
.pipe(
map((param) => +param.get('id')),
filter((id) => !!id),
switchMap((id) => this.taskService.get(id)),
tap(() => this.tags.clear()),
tap((task) => this.onAddTag(task.tags.length)),
takeUntil(this.stop$)
)
.subscribe((task) => this.form.patchValue(task));
}
ngOnDestroy(): void {
this.stop$.next();
this.stop$.complete();
}
}
这一篇透过路由传递待办事项编号,来实作待办事项的编辑功能;除此之外,ActivatedRoute 服务元件也提供 queryParamsMap
参数取得问号 (?) 後面的查询参数,以及 fragment
属性来取得井号 (#) 後面的锚点参数。
Ev3 是 LEGO® MINDSTORMS® 乐高公司发展的可程序机器人,有原生的专用语法,且可使...
前言 Hi, 我是鱼板伯爵今天要教大家 Stack(堆叠) 和 Positioned(位子),Sta...
二分搜寻法(Binary Search) 前提,在一个已经排序完成的A阵列中 Divide : 元素...
前言 档案架构是在开发前应该要先了解的事,可以让我们在对的地方做对的事情,以节省宝贵的时间。主要有四...
Leetcode #207. Course Schedule 题目给你一系列的课程,每一个门课都有它...