Các chỉ thị Angular (Angular directives) giúp chúng ta mở rộng hoặc thao tác với DOM. Chúng ta có thể thay đổi giao
diện (appearance), hành vi (behavior) hoặc bố cục (layout) của phần tử DOM bằng
cách sử dụng các directive. Trong bài này, chúng ta sẽ xây dựng một ví dụ gồm
bốn directive và đưa ra các hướng dẫn gồm:
- Tạo 1 custom directive sử dụng @Directive decorator
- Chúng ta sẽ tạo cả custom attribute directive và custom structural directive
- Cách thiết lập các selector
- Truyền data vào custom directive sử dụng @Input
- Cách phản hồi các thông tin đầu vào (input) của user
- Thao tác với phần tử DOM (thay đổi giao diện, hành vi,...)
Angular Directive
Angular có 3 loại directive:
- Components
- Structural directive: thay đổi DOM layout bằng cách thêm hoặc xóa các phần tử DOM. Tất cả các structural directive thường các có dấu * ở phía trước (ký tự Asterix).
- Attribute directives: có thể thay đổi giao diện (appearance) hoặc hành vi (behavior) của phần tử (element).
Tạo Custom Attribute Directive
Bây giờ chúng ta sẽ thực hành tạo ra một Attribute Directive cho phép thêm class vào 1 element giống như ngClass của Angular.
Tạo 1 file mới và đặt tên là add-class.directive.ts, sau đó import vào các thư viện cần thiết. Bạn có thể tạo thủ công hoặc dùng lệnh ng g directive add-class:
add-class.directive.tsimport { Directive, ElementRef, Input, OnInit } from '@angular/core';
@Directive({
selector: '[addClass]'
})
export class AddClassDirective implements OnInit {
constructor() { }
ngOnInit() { }
}
Khai báo decorator cho class với @Directive để nói với Angular rằng đây là một Directive. Và chúng ta
chọn selector là addClass và đặt tên cho directive là AddClassDirective.
Nếu bạn tạo file bằng Angular CLI thì nó sẽ tự động tạo file theo đúng cấu trúc của Angular Directive và khai báo nó vào module bao bọc nó cho bạn.
Chúng ta cần lấy class name được thêm vào element bằng directive addClass của chúng ta. Chúng ta sử dụng Input decorator chỉ định property addClass như một input property để nhận class name từ element.
Chúng ta hãy sử dụng tên cho Input decorator trùng với tên của selector để có thể thêm class name trực tiếp vào directive như thể này <button [addClass]="'blue'"> chứ không cần thiết phải dùng như này <button addClass [moreClass]="'blue'">.
add-class.directive.ts@Input() addClass: string = '';
Tiếp theo, để thay đổi được các thuộc tính của element mà chúng ta đính kèm attribute directive addClass, chúng ta phải lấy được tham chiếu (reference) của element đó. Khi chúng ta đính kèm directive của chúng ta vào element, Angular sẽ tiêm element đó vào Directive của chúng ta khi chúng ta yêu cầu một instance của ElementRef trong hàm contructor của Directive.
add-class.directive.tsconstructor(private el: ElementRef) {
}
ElementRef là một trình bao bọc xung quanh đối tượng native DOM element (HTML element - ở trường hợp của chúng ta là phần tử được đính kèm addClass attribute directive). Nó chứa thuộc tính nativeElement tham chiếu đến DOM object. Chúng ta có thể sử dụng nó để thao tác DOM.Chúng ta sử dụng ViewChid để lấy ElementRef của một HTML element trong component class. angular cũng inject ElementRef của Host element của component hoặc directive khi bạn yêu cầu nó trong hàm contructor.
Chúng ta có thể truy cập phần tử DOM thông qua thuộc tính nativeElement. Phương thức classList cho phép chúng ta thêm class vào element.
add-class.directive.tsngOnInit() {
this.el.nativeElement.classList.add(this.addClass);
}
Toàn bộ code cho addClass directive của chúng ta như sau:
add-class.directive.tsimport { Directive, ElementRef, Input, OnInit } from '@angular/core';
@Directive({
selector: '[addClass]'
})
export class AddClassDirective implements OnInit {
@Input() addClass: string = '';
constructor(private el: ElementRef) {
}
ngOnInit() {
this.el.nativeElement.classList.add(this.addClass);
}
}
app.component.scss.blue {
background-color: lightblue;
}
app.component.html<button [addClass]="'blue'">Click Me</button>
app.module.tsimport { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { AddClassDirective } from './add-class.directive';
@NgModule({
declarations: [
AppComponent,
AddClassDirective
],
imports: [
BrowserModule,
AppRoutingModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Vậy là chúng ta đã tạo xong một directive đơn giản, bắt chước ngClass directive của Angular. Bạn có thể kham thảo thêm source code của ngClass tại đây.
Tạo custom Structural Directive
Ở ví dụ này chúng ta sẽ cùng tạo ra một custom structural directive có chức năng tương tự như ngIf của Angular.
Đầu tiên, chúng ta tạo ra một mới và đặt tên là if.directive.ts và import các module cần thiết:
if.directive.tsimport { Directive, ViewContainerRef, TemplateRef, Input } from '@angular/core';
@Directive({
selector: '[appIf]'
})
export class IfDirective {
constructor() { }
}
Tạo ra một biến dùng để lưu trữ điều kiện if:
if.directive.ts_appIf!: boolean;
Vì chúng ta đang thao tác trên DOM, nên chúng ta cần có instance của ViewContainerRef và TemplateRef:
if.directive.tsconstructor(
private _viewContianer: ViewContainerRef,
private templateRef: TemplateRef<any>
) { }
Chúng ta sử dụng @Input() decorator để lấy điền kiện được input vào. Ở đây chúng ta sẽ sử dụng hàm setter vì chúng ta muốn thêm hoặc bớt nội dung bất cứ khi nào điều kiện thay đổi.
Chúng ta sử dụng tên Input giống với tên của selector directive để chúng ta có thể sử dụng property binding syntax <div *appIf="show"> trong template:
if.directive.ts@Input()
set appIf(condition: boolean) {
this._appIf = condition
this._updateView();
}
Đây là nơi tất cả những điều kỳ diệu xảy ra. Chúng tôi sử dụng phương thức createEmbeddedView của ViewContainerRef để template nếu condition là true. Và phương thức clear sẽ xóa template khỏi DOM.
if.directive.ts _updateView() {
if (this._appIf) {
this._viewContainer.createEmbeddedView(this.templateRef);
}
else {
this._viewContainer.clear();
}
}
Hãy nhớ khai báo IfDirective này của chúng ta trong app.module.ts nhé. Code hoàn chỉnh của chúng ta sẽ như sau:
if.directive.tsimport { Directive, ViewContainerRef, TemplateRef, Input } from '@angular/core';
@Directive({
selector: '[appIf]'
})
export class IfDirective {
_appIf!: boolean;
@Input()
set appIf(condition: boolean) {
this._appIf = condition
this._updateView();
}
constructor(
private _viewContainer: ViewContainerRef,
private templateRef: TemplateRef<any>
) { }
_updateView() {
if (this._appIf) {
this._viewContainer.createEmbeddedView(this.templateRef);
}
else {
this._viewContainer.clear();
}
}
}
app.component.tsimport { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title: string = "Custom Directives in Angular";
show = true;
}
app.component.html<h1> {{title}} </h1>
Show Me
<input type="checkbox" [(ngModel)]="show"> <!-- Bạn phải import FormsModule thì mới sử dụng được ngModel nhé -->
<div *appIf="show">
Using the ttIf directive
</div>
<div *ngIf="show">
Using the ngIf directive
</div>
app.module.tsimport { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { IfDirective } from './if.directive';
@NgModule({
declarations: [
AppComponent,
IfDirective
],
imports: [
BrowserModule,
AppRoutingModule,
FormsModule
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Chạy ứng dụng và so sánh ngIf & chỉ thị appIf tùy chỉnh của chúng tôi cạnh nhau.
Lưu ý:
- Bạn có để ý là chúng ta cũng sử dụng dấu * trước appIf directive của chúng ta không? Nếu bạn xóa dấu * đi, bạn sẽ gặp lỗi và directive của chúng ta cũng không hoạt động.
- Chúng ta sử dụng ký hiệu * để nói với Angular rằng: chúng có một structural directive và chúng ta sẽ thao tác với DOM. Về cơ bản, nó yêu cầu Angular inject TemplateRef. Ký hiệu * sẽ giúp Angular xác định được vị trí của template và đưa tham chiếu của nó (tham chiếu của template đính kèm appIf directive) vào IfDirective dưới dạng TemplateRef.
Tags:
angular