Template Variable và ViewChild/ViewChildren trong Angular

0 phút đọc

Nếu bạn cần trỏ tới một phần tử (HTMLElement/component/directive) ở trong template và thao tác trực tiếp lên nó thì sao. Có cách nào để chúng ta tạo ra một variable ở trong template và sử dụng nó không? Câu hỏi trên sẽ được trả lời trong bài học này

Các phần tử cha tương tác với phần tử con thông qua biến cục bộ

Giả sử chúng ta có AppComponent có nhúng một phần template như sau:

<app-toggle></app-toggle>

Nhưng thay vì click vào ToggleComponent để thay đổi trạng thái của nó thì chúng ta sẽ dùng một button ở parent (AppComponent) như template dưới đây:

<button (click)="doSomething">Toggle</button>

<br />

<app-toggle></app-toggle>

Làm thế nào để gọi được method toggle() của ToggleComponent?

Lúc này bạn sẽ có thể sử dụng đến Template variable như một giải pháp hữu hiệu.

<button (click)="toggleComp.toggle()">Toggle</button>

<br />

<app-toggle #toggleComp></app-toggle>

Cú pháp của Template variable chính là sử dụng #varName và bạn có thể tạo nhiều variable trong template.

Như trước đây làm với ví dụ về NgIf-Else chúng ta cũng đã sử dụng Template variable để lấy về một instance của ng-template và truyền vào cho NgIfElse như sau:

<div *ngIf="user.age >= 13; else noPG13">Bạn có thể xem nội dung PG-13</div>
<ng-template #noPG13>
  <div>Bạn không thể xem nội dung PG-13</div>
</ng-template>

Template variable sẽ là instance của class nào?

Thông thường, một element trong template của một component sẽ có thể là một HTMLElement, hoặc một Component. Nhưng có những trường hợp sẽ có nhiều Directives cùng được đặt vào một element, vậy Template variable sẽ cho chúng ta một object thuộc kiểu nào?

Mặc định Template variable mà không có expression #varName sẽ cố gắng lấy về một object, trong đó đối với HTMLElement thì object đó chính là HTMLElement, còn với Component thì chính là instance của Component đó.

Trong một số trường hợp bạn cần lấy chính xác một instance của một directive/component nào đó thì bạn sẽ cần sử dụng cú pháp #varName="exportAsOfDirectiveOrComponent", ví dụ như khi làm việc với FormsModulengModel, bạn có thể nhìn thấy cú pháp sau:

<form #nameForm="ngForm">
  <input
    type="text"
    class="form-control"
    required
    [(ngModel)]="model.name"
    name="name"
    #name="ngModel"
  />
  <button>Submit</button>
</form>

Ở template trên chúng ta đã tạo ra 2 Template variable là:

  • nameForm: mong muốn lấy instance của directive có exportAsngForm
  • name: mong muốn lấy instance của directive có exportAsngModel

Nếu như chúng ta không request chính xác type chúng ta mong muốn là gì thì các Template variable trên chỉ lấy về HTMLElement thông thường.

Parent class with ViewChild

Vậy nếu bạn muốn gọi đến Template variable ở trong class của một component thì sao? Giải pháp ở đây là gì, vì nếu chỉ ở ngoài template thì có vẻ nó không được mạnh mẽ cho lắm, vì code logic của chúng ta nên ở trong component (và service - sau này sẽ học) thay vì ở ngoài template.

Lúc này chúng ta có thể query một Template variable ở trong Component như sau:

<button (click)="toggleInside()">Toggle inside class</button>
<br />
<br />

<app-toggle #toggleComp></app-toggle>
export class AppComponent {
  @ViewChild("toggleComp") toggleComp: ToggleComponent;
  toggleInside() {
    this.toggleComp.toggle();
  }
}

Nếu bạn sử dụng ViewChild cho một HTMLElement thì chúng ta sẽ nhận được một ElementRef thay vì một HTMLElement như sử dụng ở trong template.

<div #chartContainer></div>
export class AppComponent {
  @ViewChild("chartContainer") container: ElementRef<HTMLDivElement>;
}

Lưu ý

Ngoài những thiết lập mặc định ở trên cho ViewChild, chúng ta còn có thể truyền vào config cho nó với các thông số chi tiết trong link sau: https://angular.io/api/core/ViewChild

Cú pháp để sử dụng sẽ như sau:

// View queries are set before the ngAfterViewInit callback is called.
ViewChild(selector: string | Function | Type<any>, opts?: {
  read?: any;
  static?: boolean;
})

Trong đó các selector có thể là:

  • Any class with the @Component or @Directive decorator
  • A template reference variable as a string (e.g. query <my-component #cmp></my-component> with @ViewChild('cmp'))
  • Any provider defined in the child component tree of the current component (e.g. @ViewChild(SomeService) someService: SomeService)
  • Any provider defined through a string token (e.g. @ViewChild('someToken') someTokenVal: any)
  • A TemplateRef (e.g. query <ng-template></ng-template> with @ViewChild(TemplateRef) template;)

opts.read có thể là bất cứ một token nào - có thể là một directive, một component, một service, etc. Nếu match với token nào thì sẽ trả về. Ví dụ:

<form #nameForm="ngForm">
  <input
    type="text"
    class="form-control"
    required
    [(ngModel)]="model.name"
    name="name"
    #name="ngModel"
  />
  <button>Submit</button>
</form>
export class NameFormComponent implements OnInit {
  model = {
    name: "Tiep Phan",
  };

  @ViewChild("nameForm", {
    read: ElementRef,
    static: true,
  })
  form: ElementRef<HTMLFormElement>;
  constructor() {}

  ngOnInit() {
    console.log(this.form);
  }
}

Ở trong trường hợp trên, nếu chúng ta không khai báo read thì sẽ lấy về NgForm instance, nhưng do khai báo là một ElementRef nên nó sẽ query khác với variable ở ngoài template.

opts.static nếu selector không nằm trong if/else hay một structure directive nào thì chúng ta có thể gọi nó là static: true, tức là nó không thay đổi trong suốt thời gian sống của component. Lúc này Angular (v9 trở lên) sẽ chạy phần resolve query result (tiến trình) trước khi chạy Change Detection nên chúng ta có thể truy cập nó ở trong ngOnInit như ở trên, nếu static: false (giá trị mặc định) thì tiến trình trên sẽ chạy sau khi chạy Change Detection nên bạn không thể dùng nó ở ngOnInit mà phải chạy ở ngAfterViewInit.

Parent class with ViewChildren

Khi bạn muốn query một danh sách các element thì bạn có thể dùng ViewChildren.

ViewChildren sẽ trả về một QueryList trước khi ngAfterViewInit được chạy. Nó sẽ chứa một số property/method để chúng ta có thể listen vào một số event (Observable).

<app-toggle></app-toggle>
<br />
<app-toggle></app-toggle>
@ViewChildren(ToggleComponent) toggleList: QueryList<ToggleComponent>;


ngAfterViewInit() {
  console.log(this.toggleList);
}

Lời kết

Như vậy trong bài học này, chúng ta cần tìm hiểu về Template variable và cách sử dụng ViewChild/ViewChildren ở trong component class. Ngoài ra, các bạn cần lưu ý các options có thể thêm vào cho ViewChild/ViewChildren theo các link dưới đây.

Cũng trong bài này chúng ta học thêm một component lifecycle khác là ngAfterViewInit để có thể thao tác được với ViewChild/ViewChildren.

Bình luận

Chưa có bình luận nào

Chưa có bình luận nào

Avatar TechMely Team
Được viết bởi

TechMely Team

Thế giới bạn không bước vào được thì đừng cố chen vào, làm khó người khác, lỡ dở mình, hà tất chứ?