Angular: Làm việc với @ViewChild

Angular @ViewChild decorator là 1 trong những decorator đầu tiên mà chúng ta sẽ học khi tìm hiểu về Angular. Nó thực sự quan trọng, hữu ích và quen thuộc trong Angular.

Trong bài viết này, chúng ta sẽ cùng tìm hiểu để thấy decorator này thực sự hữu ích như nào qua các mục dưới đây.

Nội dung chính:

Chúng ta sẽ đi qua các nội dung sau đây:

  1. Khi nào chúng ta cần đến @ViewChild decorator?
  2. Mối quan hệ với AfterViewInit Lifecycle Hook
  3. Scope của @ViewChild khi truy vấn template
  4. Sử dụng @ViewChild để truy vấn tới DOM element
  5. Sử dụng @ViewChild để truy vấn tới component
  6. @ViewChild với thuộc tính metadata
  7. Sử dụng @ViewChild để truy vấn tới directive
  8. Tổng kết

Khi nào chúng ta cần đến @ViewChild decorator?

Bạn muốn truy cập vào các child component, directive hay DOM element từ parent component. Việc này thật dễ dàng khi đã có ViewChild decorator.
ViewChild trả về phần tử đầu tiền mà chúng ta muốn truy vấn.
Trong trường hợp chúng ta muốn truy vấn tới nhiều phần từ con, chúng ta có thể dùng ViewChildren thay thế.

Mối quan hệ với AfterViewInit Lifecycle Hook

Nếu chúng ta muốn component con mà chúng ta truy vấn tới thực sự được khởi tạo thì chúng ta nên thực thi trong AfterViewInit lifecycle hook
Có thể sử dụng ngOnInit() thay thế ngAfterViewInit() hay không?
Tuỳ từng tình huống mà ta ngOnInit có thể thay thế được ngAfterViewInit khi dùng ViewChild được hay không. Nếu template được truy vấn thực sự đã sinh ra trong ngOnInit, thì chúng ta có thể dùng ngOnInit nhưng để chắc chắn và không quan tâm thời điểm template được sinh ra thì chúng ta không nên dùng ngOnInit.

Từ Angular 8+, Angular giới thiệu 1 Metadata Properties: static

static - True to resolve query results before change detection runs.
When static is not provided, uses query results to determine the timing of query resolution. If any query results are inside a nested view (such as *ngIf), the query is resolved after change detection runs. Otherwise, it is resolved before change detection runs.

-> Hiểu đơn giản thì static sẽ giúp dev kiểm soát được việc truy cập vào đối tượng được View là ở ngOnInit hay ngAfterViewInit. Static nhận vào giá trị boolean. Mặc định sẽ là false, khi đó ta không thể truy cập ở ngOnInit mà ở ngAfterViewInit

Scope của @ViewChild khi truy vấn template

Với @ViewChild, chúng ta có thể truy vấn vào trong component, directive hay DOM HTML nhưng nó thực sự truy vấn được đến độ sâu nào của cây component thì ta cần làm 1 phép thử sau đây:

@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;
}

Chúng ta có component ColorSampleComponent mà phần template có sử dụng MatIcon component.

<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>
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);
  }
}

Kết quả nếu ta chạy thử đoạn code này sẽ là: Đây là tình huống mà ta gọi 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.

Vì vậy có thể đưa ra kết luần là:
@ViewChild decorator không thể nhìn qua 1 component bị bao đóng. Điều này có nghĩa là ViewChild chỉ có thể xem được thành phần nằm bên trong template của chính nó

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
Chúng ta biến tham chiếu template demoInput với tình huống sau:

<input #demoInput placeholder="hom nay la thu may">

Chúng ta có thể sử dụng ViewChild để truy cập tới input trên như sau:

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!";
  }
}

Giá trị của Input sẽ được set thành Sunday! sau khi ngAfterViewInit thực thi.

Sử dụng @ViewChild để truy vấn tới DOM element của component

Không khó để chúng ta có thể truy cập tới component con và gọi method hoặc truy cập vào các biến có sẵn trong component con.
Chúng ta có hàm whoAmI như sau:

whoAmI() {
  return 'I am a grown-up sunner!';
}

Chúng ta có thể gọi đến method đó từ component cha khi sử dụng ViewChild như sau:

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!
  }
}

@ViewChild với thuộc tính metadata:

@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);
  }

   ....
}

Kết quả khi thực thi sẽ như sau:

Còn khi chúng ta sử dùng thuộc tính trong ViewChild

@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ả khi thực thi sẽ như sau: 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.

Sử dụng @ViewChild truy vấn Directives

Chúng ta có directive:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({ selector: '[appBeautifyName]' })
export class BeautifyNameDirective {
  name = 'htm';

  constructor(elem: ElementRef, renderer: Renderer2) {
    let name = renderer.createText('htm ');
    renderer.appendChild(elem.nativeElement, name);
  }
}

Chúng ta có thể sử dụng ViewChild để truy vấn đến directive bằng cách như sau:

import { Component,
         ViewChild,
         AfterViewInit } from '@angular/core';

import { BeautifyNameDirective } from './beautifyName.directive'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  nameApp: string;

  @ViewChild(BeautifyNameDirective)
  set appBeautifyName(directive: BeautifyNameDirective) {
    this.nameApp = directive.name;
  };

  ngAfterViewInit() {
    console.log(this.nameApp); // htm
  }
}

Tổng kết

Vậy thôi, những gì bạn cần để hiểu về ViewChild ở trên, điều bạn cần bây giờ là đọc và thử nghiệm nó và ứng dụng nó đúng trường hợp.
Cám ơn và hẹn gặp lại 🙃


Tham khảo: angular-university-angular-viewchild


All Rights Reserved