Bài này chúng ta sẽ đi vào tìm hiểu cách truy cập các thành phần bên trong một component
, directive
hoặc DOM element
bằng việc sử dụng @ViewChild
@ViewChild dùng khi nào?
@ViewChild được dùng khi chúng ta muốn truy cập vào các child component, directive hay DOM element từ parent component.
@ViewChild và @ViewChildren, hoạt động giống nhau, chỉ khác @ViewChild
trả về một đối tượng, trong khi @ViewChildren
trả về nhiều đối tượng như là một QueryList.
Cú pháp dùng @ViewChild
@ViewChild([reference from template], [metadata properties]);
#1. Diễn giải cú pháp
Thông thường, các decorator
làm việc theo cặp với các biến reference template
.
Tham số đầu tiên [reference from template] là một biến reference template, nó chỉ đơn giản là một tham chiếu có tên cho một phần tử DOM trong một template (Bạn có thể xem nó như một cái gì đó tương tự như thuộc tính id của một phần tử html).
Tham số thứ 2 [metadata property] là thuộc tính metadata, có 3 thuộc tính sau:
- selector – The directive type or the name used for querying.
- read –
{read: [reference type]}
Used to read a different token from the queried elements.
[reference type] có thể là ElementRef, TemplateRef, ViewRef, ComponentRef và ViewContainerRef - static –
{static: [true | false]}
True to resolve query results before change detection runs, false to resolve after change detection. Defaults to false.
static giúp kiểm soát được việc truy cập vào đối tượng được View là ở ngOnInit hay ngAfterViewInit. Mặc định là false, khi đó ta không thể truy cập được ởngOnInit
mà chỉ được ở ngAfterViewInit.
#2. Ví dụ
@Component({ selector: 'sample', template: ` <span #tref>I am span</span> ` }) export class SampleComponent implements AfterViewInit { @ViewChild("tref", {read: ElementRef}) tref: ElementRef; ngAfterViewInit(): void { // outputs `I am span` console.log(this.tref.nativeElement.textContent); } }
Trong ví dụ trên chúng ta đã chỉ định tref
như là một reference template
tên trong html
và nhận được ElementRef
liên kết với phần tử này.
Tham số thứ hai {read: ElementRef}
là một metadata property, tham số này có thể bỏ… vì Angular có thể suy diễn kiểu tham chiếu theo loại phần tử DOM. Ví dụ, nếu đó là một phần tử html đơn giản như span, Angular sẽ trả về ElementRef. Nếu đó là một template, nó trả về TemplateRef. Một số tham chiếu, như ViewContainerRef không thể suy luận và phải được yêu cầu cụ thể trong tham số đọc. Còn ViewRef không thể được trả lại từ DOM và phải được xây dựng bằng tay.
Việc áp dụng metadata properties trong trường hợp nào nữa thì chúng ta sẽ nghiệm ra trong quá trình code nhé!
Ví dụ sử dụng @ViewChild truy vấn tới DOM element
Chúng ta có thể truy cập tới native DOM element
thông qua biến tham chiếu template.
#1. Giả sử chúng ta có component có template…
<input #demoInput placeholder="hom nay la thu may">
#2. Sử dụng ViewChild để truy cập tới input
import { Component, ViewChild, AfterViewInit, ElementRef } from '@angular/core'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements AfterViewInit { @ViewChild('demoInput') demoInput: ElementRef; ngAfterViewInit() { this.demoInput.nativeElement.value = "Sunday!"; } }
#3. Kết quả thực thi: Giá trị của Input sẽ được set thành Sunday! sau khi ngAfterViewInit thực thi.
Ví dụ sử dụng @ViewChild truy vấn tới DOM element của child component
Không khó để chúng ta có thể truy cập tới child component và gọi method hoặc truy cập vào các biến có sẵn trong child component đó.
#1. Giả sử ta có một child component có method là whoAmI như sau:
whoAmI() { return 'I am a grown-up sunner!'; }
#2. Gọi method whoAmI này từ parent component
import { Component, ViewChild, AfterViewInit } from '@angular/core'; import { ChildComponent } from './child.component'; @Component({ selector: 'app-root', templateUrl: './app.component.html', styleUrls: ['./app.component.css'] }) export class AppComponent implements AfterViewInit { @ViewChild(ChildComponent) child: ChildComponent; ngAfterViewInit() { console.log(this.child.whoAmI()); // I am a grown-up sunner! } }
#3. Kết quả thực thi: console sẽ in ra dòng text “I am a grown-up sunner!” sau khi ngAfterViewInit thực thi.
Ví dụ về scope của @ViewChild khi truy vấn template
Xin cho nhắc lại chút lý thuyết trước khi chúng ta bắt đầu làm ví dụ về scope này
@ViewChild và @ViewChildren, hoạt động giống nhau, chỉ khác
@ViewChild
trả về một đối tượng, trong khi@ViewChildren
trả về nhiều đối tượng như là một QueryList
@ViewChild
trả về một đối tượng, vậy con cháu của đối tượng này chúng ta có thể truy cập được tới chúng không? chúng ta tiếp tục xem xét nhé…
#1. Code cho component child
@Component({ selector: 'color-sample', template: ` <div class="color-sample mat-elevation-z3" [style.background-color]="color"> <mat-icon>palette</mat-icon> </div> ` }) export class ColorSampleComponent { @Input() color; }
Ta thấy trong phần template, code có dùng MatIcon component (component cháu)
#2. Code cho app.component.html
<h2>Choose Brand Colors:</h2> <color-sample [color]="primary" #primaryColorSample ></color-sample> <mat-input-container> <mat-label>Primary Color</mat-label> <input matInput #primaryInput [(colorPicker)]="primary" [(ngModel)]="primary"/> </mat-input-container>
#3. Code cho app.component.ts
Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements AfterViewInit { @ViewChild(MatIcon) matIcon: MatIcon; ngAfterViewInit() { console.log('Values on ngAfterViewInit():'); console.log("matIcon:", this.matIcon); } }
#4. Kết quả nếu ta chạy thử đoạn code này sẽ là:
#5. Nhận xét
Đây là tình huống mà ta muốn sử dụng ViewChild để xem 1 component nằm trong component con của AppComponent, và kết quả chúng ta thấy là ViewChild không thể xem được component cháu của mình.
#6. Kết luận
Như vậy @ViewChild
chỉ có thể xem được thành phần nằm bên trong template của chính nó.
Ví dụ @ViewChild sử dụng thuộc tính metadata
Tham số thứ hai
{read: ElementRef}
là một metadata property, tham số này có thể bỏ… vì Angular có thể suy diễn kiểu tham chiếu theo loại phần tử DOM
Vậy trường hợp có và không có sử dụng thuộc tính metadata có khác nhau gì không? Chúng ta xét tiếp nhé…
#1. @ViewChild không có metadata property
@Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements AfterViewInit { @ViewChild('primaryColorSample') sample: ColorSampleComponent; ngAfterViewInit() { console.log('Values on ngAfterViewInit():'); console.log("sample:", this.sample); } /*others code*/ }
Kết quả thực thi
#2. @ViewChild có sử dụng metadata
@Component({ selector: 'app-root', templateUrl: './app.component.html' }) export class AppComponent implements AfterViewInit { @ViewChild('primaryColorSample', {read: ElementRef}) sample: ElementRef; ngAfterViewInit() { console.log('Values on ngAfterViewInit():'); console.log("sample:", this.sample.nativeElement); } }
Kết quả thực thi:
#3. Nhận xét
Khi thêm option {read: ElementRef}
thì chúng ta đã xem component
về mặt template
với cấu trúc DOM chứ không còn nhìn vào các thành phần bên trong component của nó chứa các method hay thuộc tính nào nữa.