Advanced Components In Angular 2
Xin chào các bạn.. Và khi nhắc đến ứng dụng đặc biệt là ứng dụng moblie, một điều bạn không thể bỏ qua đó là vòng đời, (như là các phương thức onCreate, onDestroy,....) Hôm nay mình xin trình bày về vòng đời và dưới cái nhìn về vòng đời của webclient mà cụ thể đó là angular 2. Đây cũng là một ...
Xin chào các bạn.. Và khi nhắc đến ứng dụng đặc biệt là ứng dụng moblie, một điều bạn không thể bỏ qua đó là vòng đời, (như là các phương thức onCreate, onDestroy,....) Hôm nay mình xin trình bày về vòng đời và dưới cái nhìn về vòng đời của webclient mà cụ thể đó là angular 2. Đây cũng là một trong những thành phần nâng cao trong angular 2 mà bạn có thể biết đến cùng với các thành phần khác như là
- Styling components
- Modifying host DOM elements
- Modifying tempates with content projection
- accessing neighbor directives
- Using lifecycle hooks
- Detecting changes
Lifecycle hooks
- Vòng đời là cách Angular cho phép bạn thêm mã chạy trước hoặc sau mỗi bước của chỉ thị hoặc vòng đời của thành phần. Angular 2 cung cấp hooks là: • OnInit • OnDestroy • DoCheck • OnChanges • AfterContentInit • AfterContentChecked • AfterViewInit • AfterViewChecked
Mỗi tên phương thức là ng cộng với tên của hook. Ví dụ, đối với OnInit chúng ta khai báo phương thức ngOnInit, cho AfterContentInit chúng ta khai báo ngAfterContentInit, .... Khi Angular biết rằng một thành phần thực hiện các chức năng này, nó sẽ gọi nó vào thời điểm thích hợp. Chúng ta hãy nhìn vào từng hook một và khi nào chúng ta sẽ sử dụng từng cái
OnInit and OnDestroy
OnInit được gọi là khi các thuộc tính chỉ thị của bạn đã được khởi tạo, và trước bất kỳ thuộc tính chỉ thị con nào được khởi tạo.Tương tự, OnDestroy được gọi khi chỉ thị chỉ thị bị hủy. Điều này thường được sử dụng nếu chúng ta cần phải làm một số dọn dẹp mỗi khi chỉ thị của chúng ta bị phá hủy. Để minh hoạ hãy viết một thành phần mà thực hiện cả hai OnInit và OnDestroy:
@Component({ selector: 'on-init', template: ` <div class="ui label"> <i class="cubes icon"></i> Init/Destroy </div> ` }) class OnInitCmp implements OnInit, OnDestroy { ngOnInit(): void { console.log('On init'); } ngOnDestroy(): void { console.log('On destroy'); } }
Đối với thành phần này, chúng ta chỉ cần khai thác OnInit and OnDestroy để hủy giao diện điều khiển khi hook được gọi. Bây giờ để kiểm tra các hooks chúng ta sử dụng thành phần của chúng ta trong component app bằng cách sử dụng ngFor để hiển thị có điều kiện nó dựa trên một boolean property. Chúng ta cũng thêm một nút cho phép chúng ta bật cờ đó. Bằng cách này, khi flag là false, thành phần của chúng tôi sẽ bị xóa khỏi trang, làm cho móc OnDestroy được gọi. Tương tự như vậy khi flag được bật sang là true, móc OnInit sẽ được gọi.
@Component({ selector: 'lifecycle-sample-app', template: ` <h4 class="ui horizontal divider header"> OnInit and OnDestroy </h4> <button class="ui primary button" (click)="toggle()"> Toggle </button> <on-init *ngIf="display"></on-init> ` }) export class LifecycleSampleApp1 { display: boolean; constructor() { this.display = true; } toggle(): void { this.display = !this.display; } }
Khi lần đầu tiên chạy ứng dụng, chúng ta có thể thấy rằng OnInit đã được gọi khi thành phần này lần đầu tiên được khởi tạo: Khi nhấp vào nút Toggle lần đầu tiên, thành phần này bị phá hủy và hook được gọi như mong đợi
OnChanges
OnChanges được gọi sau khi một hoặc nhiều thuộc tính thành phần của chúng ta đã được thay đổi. Phương thức ngOnChanges nhận được một tham số cho biết các thành phần đã thay đổi. Để hiểu điều này tốt hơn, chúng ta hãy viết một thành phần khối bình luận có hai đầu vào: name và comment
@Component({ selector: 'on-change', template: ` <div class="ui comments"> <div class="comment"> <a class="avatar"> <img src="app/images/avatars/matt.jpg"> </a> <div class="content"> <a class="author">{{name}}</a> <div class="text"> {{comment}} </div> </div> </div> </div> ` }) class OnChangeCmp implements OnChanges { @Input('name') name: string; @Input('comment') comment: string; ngOnChanges(changes: {[propName: string]: SimpleChange}): void { console.log('Changes', changes); } }
Điều quan trọng về thành phần này là nó thực thi giao diện OnChanges, và khai báo phương thức ngOnChanges với chữ ký này:
ngOnChanges(changes: {[propName: string]: SimpleChange}): void { console.log('Changes', changes); }
Phương pháp này sẽ được kích hoạt bất cứ khi nào các giá trị của tên hoặc thuộc tính bình luận thay đổi. Khi điều đó xảy ra, chúng tôi nhận được một đối tượng mà các bản đồ đã thay đổi các lĩnh vực để SimpleChange đối tượng. Mỗi cá thể SimpleChange có hai trường: currentValue và previousValue. Nếu cả tên và bình luận các thuộc tính thay đổi cho thành phần của chúng ta, chúng ta mong đợi giá trị của những thay đổi trong phương pháp của chúng ta là một cái gì đó như là:
{ name: { currentValue: 'new name value', previousValue: 'old name value' }, comment: { previousValue: 'old comment value' currentValue: 'new comment value', } }
DoCheck
Hệ thống thông báo mặc định được thực hiện bởi OnChanges được kích hoạt mỗi khi cơ chế phát hiện thay đổi góc nhìn thấy có sự thay đổi đối với bất kỳ thuộc tính chỉ dẫn nào. Tuy nhiên, có thể có lúc khi tổng phí được gửi bởi thông báo thay đổi này có thể quá nhiều, đặc biệt nếu hiệu suất là mối quan tâm. Đôi khi chúng ta chỉ muốn làm điều gì đó trong trường hợp một mục bị xóa hoặc bổ sung, hoặc nếu chỉ có một tài sản cụ thể đã thay đổi. Nếu chúng ta chạy vào một trong những kịch bản này, chúng ta có thể sử dụng DoCheck.
Kiểm tra sự thay đổi
Để đánh gia những sự thay đổi, Angular cung cấp differs. Differs sẽ tính toán các giá trị của directive đã được định trước cho với những sự thay đổi. Có 2 loại khác nhau đó là : iterable differs và key-value differs
Iterable differs
Iterable difers nên được sử dụng khi chúng ta có một cấu trúc giống như danh sách và chúng tôi chỉ quan tâm đến việc biết những thứ đã được thêm vào hoặc xóa khỏi danh sách đó.
Key-value differs
Key-value differs cần sử dụng khóa-giá trị khác cho các cấu trúc tương tự từ điển và làm việc ở cấp độ chính. Điều khác biệt này sẽ xác định các thay đổi khi một khoá mới được thêm vào, khi một khoá bị xóa và khi giá trị của một khoá thay đổi. Chúng ta cùng xem nhanh một ví dụ:
class DoCheckItem implements DoCheck { @Input() comment: any; onRemove: EventEmitter<any>; differ: any;
Class DoCheckItem được định nghĩa, có implement DoCheck interface, với các thành phần chính như là 1 thẻ input, output event là onRemove. Chúng ta cũng sẽ định nghĩa 1 thuộc tính differ.
constructor(differs: KeyValueDiffers) { this.differ = differs.find([]).create(null); this.onRemove = new EventEmitter(); }
Trên constructor, chúng ta đang nhận một cá thể KeyValueDiffers trên biến khác. Sau đó chúng ta sử dụng biến này để tạo ra một thể hiện của giá trị khóa khác nhau bằng cách sử dụng cú pháp này differs.find ([]) create (null). Chúng tôi cũng đang khởi tạo trình chiếu sự kiện trên Remove. Tiếp theo, hãy thực hiện phương pháp ngDoCheck, được yêu cầu bởi giao diện:
ngDoCheck(): void { var changes = this.differ.diff(this.comment); if (changes) { changes.forEachAddedItem(r => this.logChange('added', r)); changes.forEachRemovedItem(r => this.logChange('removed', r)); changes.forEachChangedItem(r => this.logChange('changed', r)); } }
Đây là cách bạn kiểm tra các thay đổi, nếu bạn đang sử dụng key-value khác. Bạn gọi phương thức diff, cung cấp tài sản bạn muốn kiểm tra. Trong trường hợp của chúng tôi, chúng tôi muốn biết nếu có thay đổi đối với thuộc tính nhận xét. Khi không có thay đổi nào được phát hiện, giá trị trả về sẽ không có giá trị. Bây giờ, nếu có thay đổi, chúng ta có thể gọi ba phương pháp khác nhau iterable khác nhau:
- forEachAddedItem, for keys that were added
- forEachRemovedItem, for keys that were removed
- forEachRemovedItem, for keys that were removed Mỗi phương thức sẽ gọi hàm gọi lại được cung cấp với một bản ghi. Đối với khóa-giá trị khác nhau, hồ sơ này sẽ là một thể hiện của lớp KVChangeRecord.
Các lĩnh vực quan trọng để hiểu những gì đã thay đổi là then chốt, previousValue và currentValue. Tiếp theo, chúng ta hãy viết một phương pháp mà sẽ đăng nhập vào một giao diện điều khiển một câu đẹp về những gì thay đổi
logChange(action, r) { if (action === 'changed') { console.log(r.key, action, 'from', r.previousValue, 'to', r.currentValue); } if (action === 'added') { console.log(action, r.key, 'with', r.currentValue); } if (action === 'removed') { console.log(action, r.key, '(was ' + r.previousValue + ')'); } }
Cuối cùng, hãy viết các phương pháp sẽ giúp chúng ta thay đổi mọi thứ trên thành phần của chúng tôi, để kích hoạt DoCheck:
remove(): void { this.onRemove.emit(this.comment); } clear(): void { delete this.comment.comment; } like(): void { this.comment.likes += 1; }
Phương thức remove () sẽ phát ra sự kiện chỉ ra rằng người dùng đã yêu cầu nhận xét này bị xóa, phương thức clear () sẽ xóa văn bản bình luận khỏi đối tượng bình luận, và phương thức like () sẽ tăng lên số truy cập tương tự cho bình luận.
AfterContentInit được gọi là sau khi OnInit, ngay sau khi khởi tạo nội dung của thành phần hoặc chỉ thị đã kết thúc. Các AfterContentChecked hoạt động tương tự, nhưng nó được gọi là sau khi kiểm tra chỉ thị đã kết thúc. Kiểm tra, trong ngữ cảnh này, là kiểm tra hệ thống phát hiện thay đổi. Hai móc khác: AfterViewInit và AfterViewChecked được kích hoạt ngay sau khi nội dung ở trên, ngay sau khi chế độ xem đã được khởi tạo đầy đủ. Hai móc này chỉ áp dụng cho các bộ phận, chứ không chỉ cho các chỉ thị. Ngoài ra, các móc nối AfterXXXInit chỉ được gọi một lần trong suốt vòng đời của chỉ thị, trong khi các móc nối AfterXXXChecked được gọi sau mỗi chu kỳ phát hiện sự thay đổi. Để hiểu rõ hơn về điều này, chúng ta hãy viết một thành phần khác mà logs vào console trong mỗi vòng đời. Nó cũng sẽ có một truy cập mà chúng ta có thể tăng bằng cách nhấn vào một nút:
@Component({ selector: 'afters', template: ` <div class="ui label"> <i class="list icon"></i> Counter: {{ counter }} </div> <button class="ui primary button" (click)="inc()"> Increment </button> ` }) class AftersCmp implements OnInit, OnDestroy, DoCheck, OnChanges, AfterContentInit, AfterContentChecked, AfterViewInit, AfterViewChecked { counter: number; constructor() { console.log('AfterCmp --------- [constructor]'); this.counter = 1; } inc() { console.log('AfterCmp --------- [counter]'); this.counter += 1; } ngOnInit() { console.log('AfterCmp - OnInit'); } ngOnDestroy() { console.log('AfterCmp - OnDestroy'); } ngDoCheck() { console.log('AfterCmp - DoCheck'); } ngOnChanges() { console.log('AfterCmp - OnChanges'); } ngAfterContentInit() { console.log('AfterCmp - AfterContentInit'); } ngAfterContentChecked() { console.log('AfterCmp - AfterContentChecked'); } ngAfterViewInit() { console.log('AfterCmp - AfterViewInit'); } ngAfterViewChecked() { console.log('AfterCmp - AfterViewChecked'); } }
Bây giờ hãy thêm nó vào thành phần ứng dụng, cùng với nút Toggle, như nút mà chúng tôi sử dụng cho OnDestroy:
<afters *ngIf="displayAfters"></afters> <button class="ui primary button" (click)="toggleAfters()"> Toggle </button>
Và cuối cùng là việc create component như sau:
@Component({ selector: 'lifecycle-sample-app', template: ` <h4 class="ui horizontal divider header"> OnInit and OnDestroy </h4> <button class="ui primary button" (click)="toggle()"> Toggle </button> <on-init *ngIf="display"></on-init> <h4 class="ui horizontal divider header"> OnChange </h4> <div class="ui form"> <div class="field"> <label>Name</label> <input type="text" #namefld value="{{name}}" (keyup)="setValues(namefld, commentfld)"> </div> <div class="field"> <label>Comment</label> <textarea (keyup)="setValues(namefld, commentfld)" rows="2" #commentfld>{{comment}}</textarea> </div> </div> <on-change [name]="name" [comment]="comment"></on-change> <h4 class="ui horizontal divider header"> DoCheck </h4> <do-check></do-check> <h4 class="ui horizontal divider header"> AfterContentInit, AfterViewInit, AfterContentChecked and AfterViewChecked </h4> <afters *ngIf="displayAfters"></afters> <button class="ui primary button" (click)="toggleAfters()"> Toggle </button> ` }) export class LifecycleSampleApp4 { display: boolean; displayAfters: boolean; name: string; comment: string; constructor() { // OnInit and OnDestroy this.display = true; // OnChange this.name = 'Felipe Coury'; this.comment = 'I am learning so much!'; // AfterXXX this.displayAfters = true; } setValues(namefld, commentfld) { this.name = namefld.value; this.comment = commentfld.value; } toggle(): void { this.display = !this.display; } toggleAfters(): void { this.displayAfters = !this.displayAfters; } }
Khi start ứng dụng, chúng ta sẽ nhìn thấy mỗi hook được log lên trên console: Kết quả sẽ được hiển thị như hình bên dưới.
Trên đây là phần mình tìm hiểu về vòng đời trong angular. Chi tiết hơn các bạn có thể tìm đọc tại cuốn sách: ng-book 2, Felipe Coury, Ari Lerner, Nate Murray, & Carlos Taborda