diff --git a/Day038-dynamic-component.md b/Day038-dynamic-component.md index 66ba6c3..e4bc9f8 100644 --- a/Day038-dynamic-component.md +++ b/Day038-dynamic-component.md @@ -13,6 +13,9 @@ Hay ở tình huống khác, chúng ta muốn người dùng phải là Vậy việc load động 1 component khác trong lúc runtime được thực hiện như thế nào? Điều đó dẫn ta đến bài hôm nay, **Dynamic Component** sẽ là câu trả lời phù hợp để làm việc này. +Dưới đây là kết quả của project code hôm nay + + ## Coding Practice ### Step 1: Khởi tạo project @@ -99,22 +102,38 @@ export class ExampleContainerComponent implements OnInit { Flow chính: 1. Tạo 1 ViewChild trong template. Ở đây là thẻ div **#dynamicComponent**. Đây sẽ là nơi chúng ta load những components vào ở runtime. -2. Connect **#dynamicComponent** thông qua @ViewChild. Chúng ta sẽ có 1 [ViewContainerRef](###ViewContainerRef) + + ```html +
+ ``` + +2. Connect **#dynamicComponent** thông qua **@ViewChild**. Chúng ta sẽ có 1 [ViewContainerRef](###ViewContainerRef) + + ```typescript + @ViewChild("dynamicComponent", { read: ViewContainerRef, static: true }) + containerRef: ViewContainerRef; + ``` + 3. Inject [CompanyFactoryResolver](###ComponentFactoryResolver) của Angular vào component ExampleContainerComponent. + + ```typescript + constructor(private cfr: ComponentFactoryResolver) {} + ``` + 4. Dùng Resolver connect với component nào chúng ta muốn load dynamic. => Kết quả sẽ trả về 1 [Component Factory](###ComponentFactory) -```typescript -const componentFactory = this.cfr.resolveComponentFactory( - DynamicContentOneComponent -); -``` + ```typescript + const componentFactory = this.cfr.resolveComponentFactory( + DynamicContentOneComponent + ); + ``` -Dùng **ViewContainerRef** với Component Factory chúng ta vừa tạo ở trên để load **Dynamic Component**. +5. Dùng **ViewContainerRef** với Component Factory chúng ta vừa tạo ở trên để load **Dynamic Component**. -```typescript -const componentRef = this.containerRef.createComponent(componentFactory); -``` + ```typescript + const componentRef = this.containerRef.createComponent(componentFactory); + ``` ### Step 4: Add các dynamic components vào entryComponents @@ -186,11 +205,11 @@ addDynamicCompOne() { Hiện tại code sử dụng entryComponents đã cũ và với **Angular Ivy**, chúng ta hoàn toàn không cần sử dụng nữa. Ngoài ra chúng ta có thể sử dụng **Angular Ivy** để **lazy load** các components dynamic. Code sẽ sửa như sau: -### Step 7.1: Xóa entryComponents setting in app.module.ts +#### Step 7.1: Xóa entryComponents setting in app.module.ts Các bạn hãy vào file app.module.ts xóa đi config đã set ở step 4. -### Step 7.2: Update code ở container component +#### Step 7.2: Update code ở container component - Remove 2 cái import components ở đầu file. - Sửa 2 hàm addDynamicComp @@ -216,8 +235,9 @@ Code sẽ như sau: componentRef.instance.data = "INPUT DATA 2"; } ``` + Vậy là đã xong, các bạn đã thực hiện thành công việc lazy load các dynamic components mà không phải add trực tiếp vào như ở những step đầu. -**Lưu ý**: Đối với những bạn nào dùng Angular phiên bản cũ thì nhớ update angular để sử dụng tính năng Angular Ivy. +**Lưu ý**: Đối với những bạn nào dùng Angular phiên bản cũ thì nhớ update angular để sử dụng tính năng Angular Ivy. ## Concepts @@ -226,7 +246,7 @@ Vậy là đã xong, các bạn đã thực hiện thành công việc Nó là một cái container từ đó có thể tạo ra Host View (component khi được khởi tạo sẽ tạo ra view tương ứng), và Embedded View (được tạo từ TemplateRef). Với các view được tạo đó sẽ có nơi để gắn vào (container). Container có thể chứa các container khác (ng-container chẳng hạn) tạo nên cấu trúc cây. Hay hiểu đơn giản thì nó giống như 1 DOM Element, khi đó có thể add thêm các view khác (Component, Template) vào đó. - +[TiepPhan](https://www.tiepphan.com/angular-trong-5-phut-dynamic-component-rendering/) ### ComponentFactory @@ -236,7 +256,7 @@ Container có thể chứa các container khác (ng-container chẳng hạn) t Đây là 1 class nhận vào các component để load dynamic và tạo ra 1 component factory của component đó. ViewContainerRef sẽ dùng **ComponentFactory** đó để load dynamic các components. -## Exercies +## Exercise ### 1. Replace component, not Add @@ -252,7 +272,7 @@ Cũng như thử emit event từ ViewChild và nhận, xử lý sự ki Day 38 chúng ta đã học được những concepts liên quan đến Dynamic Component. Đây là 1 tính năng quan trọng có tính ứng dụng cao. Các bạn có thể thực hành nhiều hơn thông qua các bài tập mình đưa cũng như các nguồn tài liệu mình để dưới đây. -Mục tiêu của ngày 39 sẽ là +Mục tiêu của ngày 39 sẽ là **Custom Attribute Directive**. ## Code sample diff --git a/Day039-custom-attribute-directive.md b/Day039-custom-attribute-directive.md new file mode 100644 index 0000000..ca76668 --- /dev/null +++ b/Day039-custom-attribute-directive.md @@ -0,0 +1,231 @@ +# Day 39: Custom Attribute Directive trong Angular + +## Introduction + +Qua các bài trước, các bạn cũng biết những directives trong Angular như ngFor, ngIf, ngStyle. +Trước khi bước vào bài mới hôm nay, mình xin tổng quát lại về các directives như sau: + +Có 3 loại directives trong Angular: + +**Components**: Đây là directive phổ biến nhất, cũng thường gặp nhất trong Angular. Directive này giúp chúng ta đóng gói (encapsulated), và tái sử dụng (reusable). Đây là directive với template, view. + +**Structural directives**: Đây là directive dùng để thay đổi cấu trúc của views bằng cách thêm hoặc ẩn, xóa bớt phần tử trong DOM. Ví dụ: ngFor, ngIf. + +**Attribute directives**: Đây là directive dùng để thay đổi diện mạo (style, appearance) hoặc hành vi (behavior) của các phần tử DOM hay components. Ví dụ: ngStyle, ngClass + +Và bài hôm nay của chúng ta sẽ là tạo 1 **Custom Attribute Directive** để chúng ta có thể tái sử dụng nhiều nơi trong ứng dụng. + +Dưới đây là kết quả của project code hôm nay + + + +## Coding Practice + +### Step 1: Khởi tạo project + +```sh +ng new custom-attribute-directive-demo +``` + +### Step 2: Tạo các components và directives + +```sh +ng g c example-component +``` + +```sh +ng generate directive custom-directive-demo +``` + +Một lưu ý nhỏ ở đây như sau, để sử dụng được directive này chúng ta cần khai báo nó vào module giống như cách chúng ta khai báo component. Câu lệnh generate trên đã auto khai báo directive trên declarations của AppModule cho chúng ta. + +Sau đó chúng ta add example-component vào template của app.component.html như sau: + +```html ++ {{ i + 1 }}. {{ city }} +
+``` + +File `example-container.component.ts` + +```typescript +export class ExampleContainerComponent implements OnInit { + text = ""; + cityList = ["Ha Noi", "Ho Chi Minh", "Da Nang", "Nha Trang", "Vinh Long"]; + + constructor() {} + + ngOnInit() {} +} +``` + +Khi chạy đoạn code trên, các bạn đã thấy được 1 cái input và 1 danh sách ngắn các thành phố. +Mình đã dùng `Custom Loop Directive` của mình để generate ra. + +Bây giờ chúng ta đi vào quá trình đọc hiểu code. + +#### 3.1, Mình đã làm thế nào để sử dụng directive `CustomLoop` cho component `ExampleContainer`? + +Đầu tiên các bạn nhìn vào file html của component. Các bạn thấy thay vì là *ngFor như bình thường, mình đã thay thế bằng +*ngCustomLoop. + +Các bạn vào file directive. Như bài 39 trước, tên directive nằm ở trong decorator **@Directive**, và các bạn sẽ dùng tên này mỗi khi muốn sẽ sử dụng directive có các HTML elements. (Ở đây mình sẽ sửa tên này ngCustomLoop) + +```typescript +@Directive({ + selector: "[ngCustomLoop]", +}) +``` + +#### 3.2, Mình đã truyền list các thành phố cho directive bằng cách nào? + +Các bạn thấy `Custom Loop directive` này mình nhận vào 1 Array như sau: + +```typescript +@Input("ngCustomLoopOf") itemList: Array+ {{ i + 1 }}. {{ city }} +
+``` + +Nhìn vào đây các bạn có thể đoán được biến mình truyền vào là `cityList`. Tuy nhiên, tại sao nó lại có tên `ngCustomLoopOf`. + +Đó là 1 micro syntax trong Angular. Angular đã kết hợp tên của directive (ở đây là **ngCustomLoop**) với tên identity cho biến truyền vào (ở đây là chữ **of**). + +> **ngCustomLoop + of = ngCustomLoopOf** + +(Nếu đoạn này các bạn vẫn cảm thấy chưa hiểu lắm thì đọc xuống xíu nữa, mình có giải thích tiếp) + +Các bạn sẽ còn gặp lại cách truyền value thế này vào directive ở tính năng tiếp theo của project này. + +#### 3.3, Mình đã dùng cái gì để generate ở `HTML` code từ file `Custom Loop directive`? + +Ở lifecycle _ngOnChange_ của directive này, mình dùng **ViewContainerRef** và gọi hàm **createEmbeddedView()** của nó như sau: + +```typescript +constructor( + private containerRef: ViewContainerRef, + private template: TemplateRef+ {{ i + 1 }}. {{ city }} +
+``` + +File `custom-loop.directive.ts` + +```typescript + export class CustomLoopDirective { + @Input("ngCustomLoopOf") itemList: Array+ {{ i + 1 }}. {{ city }} +
+``` + +Các bạn sẽ thấy ngoài trừ `$implicit`, mình còn thêm vào object trả ra 1 cái nữa tên là `index`. Ở đây mình đã xử lý để trả ra `index` là vị trí phần tử trong mảng + +```typescript + index: this.itemList.indexOf(item). +``` + +Vì biến này không phải `$implicit` nên muốn nhận biến này phải khai báo nhận đàng hoàng như sau trong file html `let i = index`. Ngoài ra các bạn cũng có thể dùng cách `index as i`. + +Vậy là đã xong, các bạn đã thực hiện thành công việc tạo và sử dụng 1 **Custom Structural Directive** trong Angular. Các bạn có thể dựa vào đây tiếp tục truyền thêm **@Input** vào, xử lý trả về `Object Context` nhiều và đa dạng hơn. Hoặc lắng nghe `event` ở directive này. + +Mấu chốt để tạo **Custom Structural Directive** xoay quanh ở hàm `createEmbeddedView()` của `ViewContainerRef`. Hiểu được input `TemplateRef` của hàm này từ đầu mà có, cũng như các kết quả trả ra của `Context Object`. + +## Concepts + +Bài này chủ yếu là các concepts cũ. Những concepts cần đọc như là **ViewContainerRef**, **TemplateRef**. + +## Exercise + +### 1. Implement Filter Feature like demo project + +Mình làm đến đây rồi, các bạn hãy thử code tiếp để hoàn thành tính năng filter theo chữ cái đầu như demo nhen. Không thì các bạn có thể tham khảo source code hoàn chỉnh ở dưới đây. + +## Summary + +Day 40 chúng ta đã học được những concepts và cách làm liên quan đến cách **Custom structural directive**. + +Mục tiêu của ngày 41 sẽ là những lệnh quen thuộc và các flag thường gặp trong **Angular CLI**. + +## Code sample + +- https://github.com/januaryofmine/angular-custom-structural-directive + +## References + +Các bạn có thể đọc thêm ở các bài viết sau + +- https://angular.io/guide/structural-directives#writing-your-own-structural-directives +- https://medium.com/@kay.odenthal_25114/creating-a-custom-structural-directive-with-angular-7-3e85bcf88bdf +- https://netbasal.com/understanding-angular-structural-directives-659acd0f67e +- https://blog.cloudboost.io/creating-structural-directives-in-angular-ff17211c7b28 + +## Author + +[Khanh Tiet](https://github.com/januaryofmine) + +`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day40` + +[day38]: Day038-dynamic-component.md +[day39]: Day039-custom-attribute-directive.md diff --git a/README.md b/README.md index ebe37c1..4556adc 100644 --- a/README.md +++ b/README.md @@ -42,6 +42,8 @@ Series độc quyền cung cấp bởi thành viên của Angular Vietnam về c | [Day 34: Template-driven Forms Trong Angular Part 2][day34] | | | [Day 35: Reactive Forms Trong Angular][day35] | | | [Day 38: Dynamic Component][day38] | +| [Day 39: Custom Attribute Directive][day39] | +| [Day 40: Custom Structural Directive][day40] | | | ## Youtube Playlist @@ -53,6 +55,7 @@ https://www.youtube.com/playlist?list=PLVmX3uPQtp3vXOXUOl8gDIA_43_pmIdFN - [Tiep Phan][tieppt] - [Chau Tran][nartc] - [Trung Vo][trungk18] +- [Khanh Tiet][khanhtiet] [day1]: Day001-Installation.md [day2]: Day002-AngularApp.md @@ -90,6 +93,8 @@ https://www.youtube.com/playlist?list=PLVmX3uPQtp3vXOXUOl8gDIA_43_pmIdFN [day34]: Day034-template-driven-forms-2.md [day35]: Day035-reactive-forms.md [day38]: Day038-dynamic-component.md +[day39]: Day039-custom-attribute-directive.md +[day40]: Day040-custom-structural-directive.md [day1-video]: https://youtu.be/NS6P1fpU77o [day2-video]: https://youtu.be/jgFw8tAgKNs [day3-video]: https://youtu.be/WrMywdbnQfk @@ -121,3 +126,4 @@ https://www.youtube.com/playlist?list=PLVmX3uPQtp3vXOXUOl8gDIA_43_pmIdFN [tieppt]: https://github.com/tieppt [nartc]: https://github.com/nartc [trungk18]: https://github.com/trungk18 +[khanhtiet]: (https://github.com/januaryofmine) \ No newline at end of file diff --git a/assets/day038.gif b/assets/day038.gif new file mode 100644 index 0000000..7b42421 Binary files /dev/null and b/assets/day038.gif differ diff --git a/assets/day039.gif b/assets/day039.gif new file mode 100644 index 0000000..955d9ec Binary files /dev/null and b/assets/day039.gif differ diff --git a/assets/day040.gif b/assets/day040.gif new file mode 100644 index 0000000..338422d Binary files /dev/null and b/assets/day040.gif differ