Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[DONE] Day039 content: Custom Attribute Directive #17

Open
wants to merge 5 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 36 additions & 16 deletions Day038-dynamic-component.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
![Dynamic Component Demo](assets/day038.gif)

## Coding Practice

### Step 1: Khởi tạo project
Expand Down Expand Up @@ -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
<div #dynamicComponent></div>
```

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

Expand Down Expand Up @@ -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
Expand All @@ -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

Expand All @@ -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/)
[TiepPhan](https://www.tiepphan.com/angular-trong-5-phut-dynamic-component-rendering/)

### ComponentFactory

Expand All @@ -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

Expand All @@ -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

Expand Down
230 changes: 230 additions & 0 deletions Day039-custom-attribute-directive.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,230 @@
# 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
![Dynamic Component Demo](assets/day039.gif)

## 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
<app-example-container></app-example-container>
```

### Step 3: Code directive logic

Chúng ta sẽ inject **ElementRef** vào constructor của directive như sau:

```typescript
constructor(private el: ElementRef) {
this.el.nativeElement.innerText = "DEMO TEXT";
this.el.nativeElement.style.color = "blue";
}
```

Ở đây chúng ta dùng **ElementRef** để tương tác và thay đổi các properties của DOM element. Cụ thể là **innerText** hay **style.color**.

### Step 4: Apply directive

Chúng ta apply attribute directive bằng cách add nó vào như mọi attribute bình thường khác. Tên của attribute này sẽ nằm ở decorator @Directive. (Chúng ta hoàn toàn có thể sửa lại tên chúng ta muốn. Ở đây mình đã sửa từ appCustomDirectiveDemo -> appCustomDirective)

```typescript
@Directive({
selector: "[appCustomDirective]",
})
```

Chúng ta vào file **example-component.component.html** sửa lại như sau để apply custom directive chúng ta vừa tạo vào component này.

```html
<p appCustomDirective></p>
```

Vậy là chúng ta đã hoàn thành cách tạo và sử dụng 1 custom attribute directive đơn giản.

### Step 5: Using Render2

Ở step 3, chúng ta đã dùng **ElementRef** để tương tác và thay đổi các properties của DOM element. Điều này đã được warning trong official doc của Angular là không nên vì nó sẽ tạo nguy cơ về cho XSS attacks.
Và ở chính warning đó, Angular cũng giới thiệu đến **Render2** layer. **Render2** cung cấp những apis để dùng tương tác với DOM element 1 cách an toàn. Vậy nên chúng ta sẽ update code sử dụng **Render2** như sau:

```typescript
export class CustomDirectiveDemoDirective {
constructor(private el: ElementRef, private renderer: Renderer2) {
this.customContent("DEMO TEXT", "blue");
}
private customContent(text: string, color: string) {
this.renderer.setProperty(this.el.nativeElement, "innerText", text);
this.renderer.setStyle(this.el.nativeElement, "color", color);
}
}
```

### Step 6: Interact with event of Host Element

Khi chúng ta sử dụng 1 **Custom Directive** cho 1 HTML element, thì element đó sẽ được gọi là **Host Element**.( Ở trường hợp của chúng ta Host Element chính thẻ p ở template của example component )

Vậy hiện tại chúng ta muốn xử lý event của Host Element ở directive thì chúng ta sẽ làm thế nào? Ví dụ, khi hover chuột thì đổi màu text. Ở trường hợp nào chúng ta sẽ tương tác thông qua **HostListener**.

Code chúng ta sẽ update như sau để lắng nghe và xử lý 2 event **mouseenter** and **mouseleave** trên Host Element:

```typescript
export class CustomDirectiveDemoDirective {
constructor(private el: ElementRef, private renderer: Renderer2) {
this.customContent("DEMO TEXT", "blue");
}

@HostListener("mouseenter") onMouseEnter() {
this.customContent("ON MOUSE ENTER", "orange");
}

@HostListener("mouseleave") onMouseLeave() {
this.customContent("DEMO TEXT", "blue");
}

private customContent(text: string, color: string) {
this.renderer.setProperty(this.el.nativeElement, "innerText", text);
this.renderer.setStyle(this.el.nativeElement, "color", color);
}
}
```

### Step 7: Pass values into the directive

Tiếp theo, chúng ta sẽ truyền xử lý data truyền từ Host Element vào directive. Cụ thể ở case này chúng ta sẽ truyền màu sẽ thay đổi khi hover từ thẻ p vào.

Ở thẻ p, chúng ta sẽ truyền vào như 1 attribute bình thường.

```html
<p appCustomDirective hoverColor="green"></p>
```

Ở directive, chúng ta sẽ dùng **@Input** để nhận và xử lý.

```typescript
import {
Directive,
ElementRef,
Renderer2,
HostListener,
Input,
} from "@angular/core";

@Directive({
selector: "[appCustomDirective]",
})
export class CustomDirectiveDemoDirective {
@Input() hoverColor: string;

constructor(private el: ElementRef, private renderer: Renderer2) {
this.customContent("DEMO TEXT", "blue");
}

@HostListener("mouseenter") onMouseEnter() {
this.customContent("ON MOUSE ENTER", this.hoverColor);
}

@HostListener("mouseleave") onMouseLeave() {
this.customContent("DEMO TEXT", "blue");
}

private customContent(text: string, color: string) {
this.renderer.setProperty(this.el.nativeElement, "innerText", text);
this.renderer.setStyle(this.el.nativeElement, "color", color);
}
}
```

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 Attribute Directive** trong Angular.

## Concepts

### ElementRef

Đây là 1 class trong **@angular/core** dùng để tương tác với các DOM element trong template. Tuy nhiên chúng ta không nên sử dụng trực tiếp vì vấn đề security.

### Renderer2

Đây là 1 class cung cấp những apis để tương tác với DOM Element 1 cách an toàn. Chúng ta sẽ dùng nó gọi đến ElementRef.

### HostListener

Đây là 1 decorator được định nghĩa cho việc lắng nghe 1 event của DOM. Chúng ta sẽ dùng để viết hàm xử lý khi event đó diễn ra.

Example:

```typescript
constructor(private el: ElementRef, private renderer: Renderer2) {
this.renderer.setProperty(this.el.nativeElement, "innerText", "DEMO");
}
```

## Exercise

### 1. @Input and @Input alias

Hiện tại code đang truyền vào thẻ p 2 attribute là **appCustomDirective** và **hoverColor** cho 2 mục đích khác nhau. Hãy sử dụng **property binding** để chỉ cần truyền 1 cái và làm gọn code đi.
Tham khảo: [**Pass value into directive**](https://angular.io/guide/attribute-directives#pass-values-into-the-directive-with-an-input-data-binding)

### 2. Interact more with Render2

Hãy sử dụng các apis của Render2 để biến đổi nhiều properties và styles hơn.

## Summary

Day 39 chúng ta đã học được những concepts và cách làm liên quan đến cách **Custom attribute directive**.

Mục tiêu của ngày 40 sẽ là **Custom structural directive**.

## Code sample

- https://github.com/januaryofmine/angular-custom-attribute-directive-example

## References

Các bạn có thể đọc thêm ở các bài viết sau

- https://angular.io/guide/attribute-directives#build-a-simple-attribute-directive
- https://medium.com/@nishu0505/custom-directives-in-angular-5a843f76bb96
- https://angular.io/api/core/ElementRef

## Author

[Khanh Tiet](https://github.com/januaryofmine)

`#100DaysOfCodeAngular` `#100DaysOfCode` `#AngularVietNam100DoC_Day39`

[day34]: Day034-template-driven-forms-2.md
[day38]: Day038-dynamic-component.md
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,8 @@ Series độc quyền cung cấp bởi thành viên của Angular Vietnam về c
| [Day 35: Reactive Forms Trong Angular][day35] | |
| [Day 38: Dynamic Component][day38] |
| |
| [Day 39: Custom Attribute Directive][day39] |
| |

## Youtube Playlist

Expand All @@ -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
Expand Down Expand Up @@ -90,6 +93,7 @@ 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
[day1-video]: https://youtu.be/NS6P1fpU77o
[day2-video]: https://youtu.be/jgFw8tAgKNs
[day3-video]: https://youtu.be/WrMywdbnQfk
Expand Down Expand Up @@ -121,3 +125,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)
Binary file added assets/day038.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added assets/day039.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.