Angular (#2)- Tìm hiểu về @ViewChild

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

Angular- Làm việc với @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@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à ElementRefTemplateRefViewRefComponentRef 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.

Trả lời

Email của bạn sẽ không được hiển thị công khai. Các trường bắt buộc được đánh dấu *