Angular - @ViewChild && ngAfterViewInit

Posted by Tim Lin on 2019-01-24

若想要直接取得目前 child componet 的值 (不是做 content projection)
那就要使用 @ViewChild

想要取得 <auth-message> child componet 的值的話

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<div>
<form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
<ng-content select="h3"></ng-content>
<label>
Email address
<input type="email" name="email" ngModel>
</label>
<label>
Password
<input type="password" name="password" ngModel>
</label>
<ng-content select="auth-remember"></ng-content>
<auth-message
[style.display]="(showMessage ? 'inherit' : 'none')">
</auth-message>
<ng-content select="button"></ng-content>
</form>
</div>

使用

1
@ViewChild(AuthMessageComponent) message: AuthMessageComponent;

取得 <auth-message> child componet 的值

並 impplements AfterViewInit, 實作 ngAfterViewInit() 方法

在這想要直接修改 <auth-message> component 內的 message 值

1
2
3
ngAfterViewInit() {
this.message.days = 30;
}

但卻發現會噴錯!

這個錯誤原因應該是因為

在dev模式下,額外增加了一次變化檢測,在第一輪變化檢測週期結束後,會立即進行第二輪變化檢測,對比兩次檢測值,如果不相同,則認為是變化檢測引起的。
reference by: 用 ngif 多次判斷 Expression has changed after it was checked

所以會噴這樣的錯誤

所以解決辦法是把這段移到 ngAfterContentInit() 提前修改值, 就不會噴錯了
(因為 ngAfterContentInit() 發生在 ngAfterViewInit() 之前)

1
2
3
4
5
ngAfterContentInit() {
if (this.message) {
this.message.days = 30;
}
}

auth-form.componet.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { Component, Output, ViewChild, AfterViewInit, EventEmitter, ContentChildren, QueryList, AfterContentInit } from '@angular/core';

import { AuthRememberComponent } from './auth-remember.component';
import { AuthMessageComponent } from './auth-message.component';

import { User } from './auth-form.interface';

@Component({
selector: 'auth-form',
template: `
<div>
<form (ngSubmit)="onSubmit(form.value)" #form="ngForm">
<ng-content select="h3"></ng-content>
<label>
Email address
<input type="email" name="email" ngModel>
</label>
<label>
Password
<input type="password" name="password" ngModel>
</label>
<ng-content select="auth-remember"></ng-content>
<auth-message
[style.display]="(showMessage ? 'inherit' : 'none')">
</auth-message>
<ng-content select="button"></ng-content>
</form>
</div>
`
})
export class AuthFormComponent implements AfterContentInit, AfterViewInit {

showMessage: boolean;

@ViewChild(AuthMessageComponent) message: AuthMessageComponent;

@ContentChildren(AuthRememberComponent) remember: QueryList<AuthRememberComponent>;

@Output() submitted: EventEmitter<User> = new EventEmitter<User>();

ngAfterViewInit() {
//this.message.days = 30;
}

ngAfterContentInit() {
if (this.message) {
this.message.days = 30;
}
if (this.remember) {
this.remember.forEach((item) => {
item.checked.subscribe((checked: boolean) => this.showMessage = checked);
});
}
}

onSubmit(value: User) {
this.submitted.emit(value);
}

}

auth-message.component.ts

1
2
3
4
5
6
7
8
9
10
11
12
13
import { Component } from '@angular/core';

@Component({
selector: 'auth-message',
template: `
<div>
You will be logged in for {{ days }} days
</div>
`
})
export class AuthMessageComponent {
days: number = 7;
}

Result

Reference