+8

Angular Bài 4 - Directives

1. Directives trong Angular

Angular cung cấp cho chúng ta một cấu trúc gọi là Directives. Directives không phải là hướng dẫn hay chỉ dẫn như nghĩa được dịch ra tiếng Việt của chúng ta hiểu. Mà nó chính là một thành phần giúp chúng ta tạo ra các hành vi tùy chỉnh trong DOM (Document Object Model).

Angular cung cấp cho chúng ta 3 loại Directives:

  • Components: Như các bạn đã biết, đây là các directive có template. Các bạn có thể nghĩ đến nó như là các khối xây dựng UI của ứng dụng Angular.

  • Attribute directives: Đây là các directive thay đổi hành vi hoặc khả năng của các phần tử DOM, component hoặc directive khác.

  • Structural directives: Đây là loại directive thay đổi layout bằng cách thêm và loại bỏ các phần tử DOM.

Mình sẽ nói rõ hơn về các loại Directives này sau. Nhưng trước hết, hãy cùng mình xem cách sử dụng các directive sẵn có.

1.1 Sử dụng các Directives có sẵn

Angular cung cấp cho chúng ta một số Directives sẵn có rất hữu ích. Các bạn không cần phải viết thêm code, chỉ cần biết cách sử dụng chúng là đã có thể tạo ra các ứng dụng Angular mạnh mẽ rồi. Một số directive sẵn có mà mình muốn nói đến bao gồm: NgIf, NgForNgClass.

  • NgIf: Một Structural Directives giúp thêm hoặc loại bỏ một phần tử DOM dựa trên một điều kiện. Ví dụ:
<div *ngIf="isLoading">Loading...</div>

Ở đây, mình đang tạo một div chứa text là "Loading...". Div này sẽ chỉ hiển thị khi biến isLoadingtrue.

  • NgFor: Directive này giúp chúng ta tạo ra một danh sách các phần tử DOM dựa trên một mảng. Ví dụ:
<div *ngFor="let item of items">{{item}}</div>

Ở đây, mình sử dụng NgFor để tạo ra một div cho mỗi phần tử trong mảng items.

  • NgClass: Đây là một Attribute Directives, giúp chúng ta thay đổi class của một phần tử DOM dựa trên một điều kiện. Ví dụ:
<div [ngClass]="{'active': isActive}">Hello World</div>

Ở đây, class active sẽ được thêm vào div nếu biến isActivetrue.

Đến đây thì chắc các bạn đã hiểu cách sử dụng các Directives có sẵn rồi nhỉ? Nhưng nếu chúng ta muốn tạo một hành vi tùy chỉnh thì sao? Đúng rồi, chúng ta sẽ tạo ra một Custom Directive.

1.2 Tạo Custom Directives

Các directive có sẵn rất tiện lợi, nhưng đôi khi chúng ta cần thực hiện một số hành vi tùy chỉnh phức tạp hơn. Đó là lúc chúng ta cần tới Custom Directives. Đừng lo, việc tạo ra một Custom Directives không hề khó khăn.

Giả sử chúng ta muốn tạo một directive có tên là highlight. Directive này sẽ thay đổi màu nền của một phần tử DOM thành màu vàng. Dưới đây là cách chúng ta tạo ra directive này.

Trước tiên, hãy tạo một file mới với tên highlight.directive.ts và dán đoạn code sau vào file này:

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'yellow';
  }
}

Trong đoạn code trên, chúng ta đã tạo ra một directive mới có tên là highlight. Để sử dụng directive này, chúng ta chỉ cần thêm attribute highlight vào phần tử DOM muốn thay đổi màu nền.

Giờ hãy thử dùng directive mới tạo này trong một component:

<div highlight>Hello World</div>

Kết quả là, chúng ta sẽ có một div có nền màu vàng. Quá đơn giản phải không nào?

Có lẽ bạn đang thắc mắc về ElementRef mà chúng ta đã sử dụng trong directive. Đừng lo, mình sẽ giải thích ngay đây.

2. ElementRef và Renderer2

ElementRef là một class của Angular, giúp chúng ta thao tác trực tiếp lên các phần tử DOM. Trong ví dụ vừa rồi, chúng ta đã sử dụng ElementRef để thay đổi màu nền của phần tử DOM.

Tuy nhiên, tốt hơn thì chúng ta không nên sử dụng ElementRef để thao tác trực tiếp lên DOM, vì nó có thể gây ra các vấn đề về bảo mật. Thay vào đó, chúng ta nên sử dụng Renderer2, một class khác của Angular, giúp chúng ta thao tác lên DOM một cách an toàn hơn.

Dưới đây là cách chúng ta sử dụng Renderer2 để tạo ra directive highlight:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef, private renderer: Renderer2) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
  }
}

Như bạn thấy, với Renderer2, chúng ta có thể thao tác lên DOM một cách an toàn hơn mà không làm ảnh hưởng đến bảo mật của ứng dụng.

Ở trên mình đã nói rằng chúng ta không nên sử dụng ElementRef để thao tác trực tiếp lên DOM, vì nó có thể gây ra các vấn đề về bảo mật. Để hiểu rõ hơn, chúng ta cần nắm bắt được cách hoạt động của ElementRef và tại sao nó lại có thể gây ra các vấn đề bảo mật.

ElementRef là một wrapper (bọc, đóng gói) cho một phần tử DOM. Khi bạn sử dụng ElementRef.nativeElement để truy cập trực tiếp đến một phần tử DOM, bạn sẽ mở ra khả năng thao tác lên DOM một cách trực tiếp. Mặc dù việc này mang lại sự linh hoạt, nhưng nó cũng tạo ra một lỗ hổng bảo mật: nếu kẻ tấn công có thể chèn mã độc vào ứng dụng của bạn (thông qua một lỗ hổng XSS, chẳng hạn), họ có thể sử dụng ElementRef.nativeElement để thao tác lên DOM và gây ra những hậu quả nghiêm trọng.

Ví dụ, giả sử rằng ứng dụng của bạn cho phép người dùng nhập vào một đoạn văn bản, và sau đó hiển thị đoạn văn bản này lên trang web:

@Directive({
  selector: '[appDangerousDirective]'
})
export class DangerousDirective {
  constructor(private el: ElementRef) {
    this.el.nativeElement.innerHTML = 'Đây là một đoạn văn bản mà người dùng nhập vào';
  }
}

Nếu kẻ tấn công có thể chèn mã độc vào đoạn văn bản mà người dùng nhập vào, mã độc đó sẽ được thực thi khi bạn sử dụng ElementRef.nativeElement.innerHTML.

Để giải quyết vấn đề này, Angular cung cấp Renderer2, một API giúp bạn thao tác lên DOM một cách an toàn. Renderer2 giúp chúng ta thao tác lên DOM mà không phải tiếp xúc trực tiếp với phần tử DOM. Thay vào đó, chúng ta sử dụng các phương thức của Renderer2, như createElement, appendChild, setProperty, và nhiều phương thức khác.

Một điểm quan trọng nữa về Renderer2 là nó không chỉ giúp bảo vệ ứng dụng của bạn khỏi các lỗ hổng bảo mật, mà còn giúp ứng dụng của bạn trở nên platform-agnostic. Platform-agnostic??? nó có nghĩa là, nếu bạn thao tác lên DOM bằng Renderer2 thay vì ElementRef, ứng dụng của bạn sẽ có thể chạy trên nhiều nền tảng khác nhau, chẳng hạn như trên web, mobile, hoặc server-side rendering, mà không cần phải thay đổi mã nguồn.

Đây là ví dụ về cách sử dụng Renderer2 để tạo ra một phần tử div và thêm nó vào DOM:

import { Directive, Renderer2, ElementRef } from '@angular/core';

@Directive({
  selector: '[appSafeDirective]'
})
export class SafeDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('Đây là một đoạn văn bản an toàn');

    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
  }
}

3. Directives và RxJs

Trước hết, để giúp các bạn dễ hiểu hơn, mình sẽ giới thiệu sơ qua về RxJS. RxJS (Reactive Extensions for JavaScript) là một thư viện giúp chúng ta làm việc với các sự kiện và luồng dữ liệu bất đồng bộ (asynchronous data streams) thông qua các toán tử và Observables.

Chúng ta có thể sử dụng RxJS trong Directives để quản lý việc cập nhật dữ liệu. Điểm mạnh của việc này là chúng ta có thể tận dụng được các toán tử của RxJS để thao tác và biến đổi dữ liệu một cách linh hoạt và mạnh mẽ.

Giả sử bạn muốn tạo một Directives để thao tác màu sắc của một phần tử, và muốn màu sắc này thay đổi mỗi giây. Bạn có thể sử dụng RxJS để tạo ra một luồng dữ liệu (stream) của các giá trị màu sắc, và sau đó sử dụng Directives để áp dụng các giá trị màu sắc này lên phần tử:

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Directive({
  selector: '[appColorChanger]'
})
export class ColorChangerDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const colorStream = interval(1000).pipe(
      map(() => '#' + Math.floor(Math.random()*16777215).toString(16))
    );

    colorStream.subscribe(color => {
      this.renderer.setStyle(this.el.nativeElement, 'color', color);
    });
  }
}

Ở đây, chúng ta tạo ra một luồng dữ liệu colorStream sử dụng interval(1000), tức là một giá trị mới sẽ được sinh ra mỗi giây. Giá trị này sẽ được biến đổi bằng hàm map() để trở thành một giá trị màu sắc ngẫu nhiên. Cuối cùng, chúng ta sẽ sử dụng subscribe() để áp dụng màu sắc lên phần tử mỗi khi có giá trị mới từ colorStream.

Để hình dung rõ hơn, mình sẽ đưa ra một ví dụ khác. Giả sử bạn đang xây dựng một ứng dụng chat và muốn người dùng nhận được thông báo mỗi khi có tin nhắn mới. Trong trường hợp này, bạn có thể tạo một Directive để thao tác DOM (ví dụ: hiển thị một thông báo trên màn hình), và sử dụng RxJS để quản lý luồng dữ liệu của các tin nhắn.

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Directive({
  selector: '[appMessageNotifier]'
})
export class MessageNotifierDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const messageStream = of('Tin nhắn mới từ Em Trinh!').pipe(delay(5000));

    messageStream.subscribe(message => {
      const div = this.renderer.createElement('div');
      const text = this.renderer.createText(message);

      this.renderer.addClass(div, 'notification');
      this.renderer.appendChild(div, text);
      this.renderer.appendChild(this.el.nativeElement, div);
    });
  }
}

Trong ví dụ trên, messageStream là một Observable, mô phỏng việc nhận một tin nhắn mới sau 5 giây. Khi tin nhắn đến, chúng ta sẽ tạo một div với class notification, thêm tin nhắn vào div này, và cuối cùng thêm div vào DOM.

4. So sánh giữa Directives và Component

Có thể nói, Directives và Component là hai khái niệm quan trọng nhất trong Angular. Tuy nhiên, Directives và Component có những điểm khác biệt quan trọng:

  • Component là một loại Directives đặc biệt, có thể coi là một Directives có thêm template. Mỗi Component đều có một view riêng, và có thể chứa các Component con.

  • Directives không có template, chúng chỉ định hành vi cho các phần tử DOM hoặc Component mà chúng được gắn vào.

  • Component thì thường được dùng để tạo ra các khối UI, trong khi Directives thường được dùng để tạo ra hành vi tùy chỉnh cho các phần tử DOM hoặc Component.

Đó là tất cả những gì mình muốn chia sẻ với các bạn về Directives trong Angular. Bây giờ hãy cùng mình đi vào phần cuối cùng của bài viết để ôn tập lại những thứ ở trên nhé.

5. Câu hỏi ôn tập

5.1 Tại sao tôi nên sử dụng Directives?

Directives giúp chúng ta tạo ra các hành vi tùy chỉnh cho các phần tử DOM hoặc Component. Điều này giúp chúng ta tận dụng tối đa khả năng của Angular và tạo ra các ứng dụng phong phú và linh hoạt.

5.2 Khi nào tôi nên sử dụng Custom Directives?

Bạn nên sử dụng Custom Directives khi các Directives có sẵn của Angular không đáp ứng được nhu cầu của bạn. Với Custom Directives, bạn có thể tạo ra bất kỳ hành vi tùy chỉnh nào mà bạn muốn.

5.3 Tại sao tôi nên sử dụng Renderer2 thay vì ElementRef?

Bạn nên sử dụng Renderer2 thay vì ElementRef để thao tác trên DOM vì Renderer2 giúp bạn thao tác trên DOM một cách an toàn hơn. Sử dụng ElementRef có thể gây ra các vấn đề về bảo mật.

5.4 Tôi có thể sử dụng Directives và RxJs như thế nào?

Bạn có thể sử dụng RxJs để quản lý việc cập nhật dữ liệu trong Directives. Ví dụ, bạn có thể tạo ra một Observable từ một sự kiện DOM và sau đó subscribe Observable này trong Directive của bạn.

5.5 Có thể tạo ra một Directives mà không cần sử dụng @Directive không?

Không, bạn không thể tạo ra một Directives mà không sử dụng @Directive. Decorator @Directive cho Angular biết đây là một Directives và cung cấp các thông tin cần thiết để Angular có thể quản lý Directives đó.

Hy vọng bài viết này sẽ giúp các bạn hiểu rõ hơn về Directives trong Angular. Hãy tiếp tục theo dõi series bài viết của mình để cùng khám phá thêm về Angular nhé!


English Version

1. Directives in Angular

Angular provides us with a structure called Directives. Directives are not instructions or guidelines as the word suggests in English. Instead, they are components that help us create custom behaviors in the Document Object Model (DOM).

Angular offers us three types of Directives:

  • Components: As you may already know, these are directives with templates. You can think of them as building blocks for the UI of an Angular application.

  • Attribute directives: These directives change the behavior or capabilities of DOM elements, components, or other directives.

  • Structural directives: This type of directive changes the layout by adding or removing DOM elements.

I will explain more about these types of directives later. But first, let's see how to use the available directives.

1.1 Using Available Directives

Angular provides us with some useful built-in directives. You don't have to write additional code; just knowing how to use them allows you to create powerful Angular applications. Some of the available directives I want to mention include NgIf, NgFor, and NgClass.

  • NgIf: It is a structural directive that adds or removes a DOM element based on a condition. For example:
<div *ngIf="isLoading">Loading...</div>

Here, I'm creating a div that contains the text "Loading...". This div will only be displayed when the isLoading variable is true.

  • NgFor: This directive helps us create a list of DOM elements based on an array. For example:
<div *ngFor="let item of items">{{item}}</div>

Here, I'm using NgFor to create a div for each element in the items array.

  • NgClass: It is an attribute directive that allows us to change the class of a DOM element based on a condition. For example:
<div [ngClass]="{'active': isActive}">Hello World</div>

Here, the active class will be added to the div if the isActive variable is true.

By now, you should understand how to use the available directives, right? But what if we want to create a custom behavior? Yes, that's right, we will create a Custom Directive.

1.2 Creating Custom Directives

Built-in directives are convenient, but sometimes we need to perform more complex custom behaviors. That's when we need Custom Directives. Don't worry, creating a Custom Directive is not difficult.

Let's say we want to create a directive called highlight. This directive will change the background color of a DOM element to yellow. Here's how we create this directive.

First, create a new file named highlight.directive.ts and paste the following code into it:

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'yellow';
  }
}

In the above code, we have created a new directive named highlight. To use this directive, we just need to add the highlight attribute to the DOM element we want to change the background color of.

Now let's try using this newly created directive in a component:

<div highlight>Hello World</div>

The result is that we will have a div with a yellow background color. It's that simple, isn't it?

You may be wondering about the ElementRef we used in the directive. Don't worry, I will explain it right here.

2. ElementRef and Renderer2

ElementRef is a class in Angular that allows us to directly manipulate DOM elements. In the previous example, we used ElementRef to change the background color of a DOM element.

However, it is better not to use ElementRef for direct DOM manipulation because it can introduce security vulnerabilities. Instead, we should use Renderer2, another class in Angular that allows us to safely manipulate the DOM.

Here's how we use Renderer2 to create the highlight directive:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef, private renderer: Renderer2) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
  }
}

As you can see, with Renderer2, we can manipulate the DOM more safely without compromising the security of the application.

Earlier, I mentioned that using ElementRef for direct DOM manipulation can introduce security vulnerabilities. To understand this better, we need to grasp how ElementRef works and why it can lead to security issues.

ElementRef is a wrapper for a DOM element. When you use ElementRef.nativeElement to access a DOM element directly, you open up the possibility of direct DOM manipulation. While this provides flexibility, it also creates a security hole: if an attacker can inject malicious code into your application (through an XSS vulnerability, for example), they can use ElementRef.nativeElement to manipulate the DOM and cause serious consequences.

For example, let's assume your application allows users to input text, which is then displayed on the website:

@Directive({
  selector: '[appDangerousDirective]'
})
export class DangerousDirective {
  constructor(private el: ElementRef) {
    this.el.nativeElement.innerHTML = 'This is user-entered text';
  }
}

If an attacker can inject malicious code into the user-entered text, that code will be executed when you use ElementRef.nativeElement.innerHTML.

To address this issue, Angular provides Renderer2, an API that allows you to manipulate the DOM safely. Renderer2 enables us to manipulate the DOM without direct access to the DOM elements. Instead, we use methods provided by Renderer2, such as createElement, appendChild, setProperty, and many others.

Another important aspect of Renderer2 is that it not only helps protect your application from security vulnerabilities but also makes your application platform-agnostic. Platform-agnostic? It means that if you manipulate the DOM using Renderer2 instead of ElementRef, your application can run on different platforms, such as the web, mobile, or server-side rendering, without changing the source code.

Here's an example of using Renderer2 to create a div element and append it to the DOM:

import { Directive, Renderer2, ElementRef } from '@angular/core';

@Directive({
  selector: '[appSafeDirective]'
})
export class SafeDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('This is a safe text');

    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
  }
}

3. Directives and RxJs

First, to make it easier to understand, let me briefly introduce RxJS. RxJS (Reactive Extensions for JavaScript) is a library that helps us work with asynchronous data streams and events through operators and Observables.

We can use RxJS in Directives to manage data updates. The strength of this approach is that we can leverage the RxJS operators to manipulate and transform data in a flexible and powerful way.

Let's say you want to create a directive that manipulates the color of an element and wants this color to change every second. You can use RxJS to create a data stream of color values and then use Directives to apply these color values to the element:

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Directive({
  selector: '[appColorChanger]'
})
export class ColorChangerDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const colorStream = interval(1000).pipe(
      map(() => '#' + Math.floor(Math.random() * 16777215).toString(16))
    );

    colorStream.subscribe(color => {
      this.renderer.setStyle(this.el.nativeElement, 'color', color);
    });
  }
}

Here, we create a colorStream data stream using interval(1000), which emits a new value every second. This value is transformed using the map() function to become a random color value. Finally, we use subscribe() to apply the color to the element whenever there is a new value from colorStream.

To get a clearer picture, let me provide another example. Suppose you are building a chat application and want users to receive notifications whenever there is a new message. In this case, you can create a Directive to manipulate the DOM (e.g., displaying a notification on the screen) and use RxJS to manage the data stream of messages.

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Directive({
  selector: '[appMessageNotifier]'
})
export class MessageNotifierDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const messageStream = of('New message from Em Trinh!').pipe(delay(5000));

    messageStream.subscribe(message => {
      const div = this.renderer.createElement('div');
      const text = this.renderer.createText(message);

      this.renderer.addClass(div, 'notification');
      this.renderer.appendChild(div, text);
      this.renderer.appendChild(this.el.nativeElement, div);
    });
  }
}

In the above example, messageStream is an Observable that simulates receiving a new message after 5 seconds. When the message arrives, we create a div element with the class notification, add the message to this div, and finally append the div to the DOM.

4. Comparison between Directives and Components

It can be said that Directives and Components are the two most important concepts in Angular. However, Directives and Components have significant differences:

  • Components are a special type of Directives with templates. Each component has its own view and can contain child components.

  • Directives do not have templates; they only specify behavior for DOM elements or components they are applied to.

  • Components are typically used to create UI blocks, while Directives are often used to create custom behaviors for DOM elements or components.

That's all I want to share with you about Directives in Angular. Now let's move on to the final part of the article to review what we've covered.

5. Review Questions

5.1 Why should I use Directives?

Directives help us create custom behaviors for DOM elements or components. This allows us to maximize the capabilities of Angular and create rich and flexible applications.

5.2 When should I use Custom Directives?

You should use Custom Directives when the built-in directives provided by Angular do not meet your needs. With Custom Directives, you can create any custom behavior you want.

5.3 Why should I use Renderer2 instead of ElementRef?

You should use Renderer2 instead of ElementRef for DOM manipulation because Renderer2 allows you to manipulate the DOM more safely. Using ElementRef directly can introduce security vulnerabilities.

5.4 How can I use Directives and RxJs together?

You can use RxJs to manage data updates in Directives. For example, you can create an Observable from a DOM event and then subscribe to that Observable in your Directive.

5.5 Can I create a Directive without using @Directive?

No, you cannot create a Directive without using @Directive. The @Directive decorator lets Angular know that this is a Directive and provides the necessary information for Angular to manage that Directive.

I hope this article helps you gain a better understanding of Directives in Angular. Stay tuned for more articles in my series to explore Angular further!


日本語版

1. Angularにおけるディレクティブ

Angularでは、ディレクティブと呼ばれる構造が提供されています。ディレクティブは、英語の意味する指示やガイドラインではありません。実際には、Document Object Model(DOM)内でカスタムな動作を作成するためのコンポーネントです。

Angularには次の3つのタイプのディレクティブがあります。

  • コンポーネント: おそらく既に知っているように、これらはテンプレートを持つディレクティブです。AngularアプリケーションのUIの構築ブロックと考えることができます。

  • 属性ディレクティブ: これらのディレクティブは、DOM要素、コンポーネント、他のディレクティブの動作や機能を変更します。

  • 構造ディレクティブ: このタイプのディレクティブは、DOM要素を追加または削除することでレイアウトを変更します。

これらのディレクティブの詳細については後ほど説明します。まずは、利用可能なディレクティブの使い方を見てみましょう。

1.1 利用可能なディレクティブの使用方法

Angularには、いくつかの便利な組み込みディレクティブが用意されています。追加のコードを書く必要はありません。これらのディレクティブの使い方を知っていれば、強力なAngularアプリケーションを作成することができます。言及したいいくつかの利用可能なディレクティブには、NgIfNgForNgClassなどがあります。

  • NgIf: 条件に基づいてDOM要素を追加または削除する構造ディレクティブです。例えば:
<div *ngIf="isLoading">読み込み中...</div>

ここでは、テキスト「読み込み中...」を含むdiv要素を作成しています。このdiv要素は、isLoading変数がtrueの場合にのみ表示されます。

  • NgFor: このディレクティブは、配列を基にDOM要素のリストを作成するのに役立ちます。例えば:
<div *ngFor="let item of items">{{item}}</div>

ここでは、items配列の各要素ごとにdiv要素を作成しています。

  • NgClass: 条件に基づいてDOM要素のクラスを変更する属性ディレクティブです。例えば:
<div [ngClass]="{'active': isActive}">こんにちは、世界</div>

ここでは、isActive変数がtrueの場合、div要素にactiveクラスが追加されます。

これまでのところ、利用可能なディレクティブの使い方がわかりましたか?では、カスタムな動作を作成したい場合はどうなるでしょうか?そうです、カスタムディレクティブを作成します。

1.2 カスタムディレクティブの作成

組み込みのディレクティブは便利ですが、より複雑なカスタムな動作が必要な場合もあります。そのような場合には、カスタムディレクティブが必要です。心配しないでください、カスタムディレクティブを作成するのは難しくありません。

highlightというディレクティブを作成したいとしましょう。このディレクティブは、DOM要素の背景色を黄色に変更するものです。以下にこのディレクティブの作成方法を示します。

まず、highlight.directive.tsという新しいファイルを作成し、次のコードを参照してください。

import { Directive, ElementRef } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef) {
    el.nativeElement.style.backgroundColor = 'yellow';
  }
}

上記のコードでは、highlightという新しいディレクティブを作成しました。このディレクティブを使用するには、背景色を変更したいDOM要素にhighlight属性を追加するだけです。

では、この新しく作成したディレクティブをコンポーネントで使用してみましょう。

<div highlight>こんにちは、世界</div>

結果として、背景色が黄色のdivが表示されます。簡単ですね。

ディレクティブで使用したElementRefについて疑問に思っているかもしれません。心配しないでください、ここで説明します。

2. ElementRefとRenderer2

ElementRefは、AngularでDOM要素を直接操作するためのクラスです。前の例では、ElementRefを使用してDOM要素の背景色を変更しました。

ただし、直接DOM操作にElementRefを使用することは避けた方が良いです。なぜなら、セキュリティ上の脆弱性を引き起こす可能性があるからです。その代わりに、DOMを安全に操作するためのAngularの別のクラスであるRenderer2を使用するべきです。

では、highlightディレクティブを作成するためにRenderer2を使用する方法を見てみましょう。

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[highlight]'
})
export class HighlightDirective {
  constructor(private el: ElementRef, private renderer: Renderer2) {
    this.renderer.setStyle(this.el.nativeElement, 'backgroundColor', 'yellow');
  }
}

ご覧の通り、Renderer2を使ってDOMをより安全に操作することができます。ElementRefを直接使用するよりもセキュリティを保護しながらDOMを操作することができます。

先ほど、ElementRefを直接使用してDOM操作を行うことはセキュリティ上の脆弱性を引き起こす可能性があると述べました。これをより良く理解するためには、ElementRefの動作となぜセキュリティ上の問題が生じる可能性があるのかを把握する必要があります。

ElementRefはDOM要素のラッパーです。ElementRef.nativeElementを使用して直接DOM要素にアクセスすると、直接DOM操作が可能になります。これにより柔軟性は高まりますが、セキュリティホールも作成されます。たとえば、悪意のあるコードをアプリケーションに注入できる場合(例えば、XSSの脆弱性を介して)、ElementRef.nativeElementを使用してDOMを操作し、重大な結果を引き起こすことができます。

例えば、アプリケーションがユーザーからのテキスト入力を許可し、それをウェブサイト上に表示するとします。

@Directive({
  selector: '[appDangerousDirective]'
})
export class DangerousDirective {
  constructor(private el: ElementRef) {
    this.el.nativeElement.innerHTML = 'これはユーザーが入力したテキストです';
  }
}

攻撃者がユーザーが入力したテキストに悪意のあるコードを注入できる場合、ElementRef.nativeElement.innerHTMLを使用すると、そのコードが実行されます。

この問題を解決するために、AngularはDOMを安全に操作するためのRenderer2を提供しています。Renderer2を使用すると、DOM要素に直接アクセスすることなく、DOMを操作することができます。代わりに、Renderer2が提供するcreateElementappendChildsetPropertyなどのメソッドを使用します。

Renderer2のもう一つの重要な側面は、セキュリティ上の脆弱性だけでなく、アプリケーションをプラットフォームに依存しないものにするという点です。プラットフォームに依存しない?これは、ElementRefではなくRenderer2を使用してDOMを操作すると、ソースコードを変更せずにウェブ、モバイル、サーバーサイドレンダリングなど、異なるプラットフォームでアプリケーションを実行できることを意味します。

Renderer2を使用してdiv要素を作成し、DOMに追加する例を次に示します。

import { Directive, Renderer2, ElementRef } from '@angular/core';

@Directive({
  selector: '[appSafeDirective]'
})
export class SafeDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const div = this.renderer.createElement('div');
    const text = this.renderer.createText('これは安全なテキストです');

    this.renderer.appendChild(div, text);
    this.renderer.appendChild(this.el.nativeElement, div);
  }
}

3. ディレクティブとRxJS

まず、理解を容易にするために、RxJSについて簡単に説明します。RxJS(Reactive Extensions for JavaScript)は、演算子やObservableを介して非同期データストリームやイベントを扱うためのライブラリです。

ディレクティブ内でRxJSを使用してデータの更新を管理することができます。このアプローチの利点は、RxJSの演算子を使用してデータを柔軟で強力な方法で操作および変換できることです。

例えば、要素の色を操作するディレクティブを作成し、この色を1秒ごとに変更したいとします。RxJSを使用して色の値のデータストリームを作成し、その後ディレクティブを使用してこの色の値を要素に適用できます。

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { interval } from 'rxjs';
import { map } from 'rxjs/operators';

@Directive({
  selector: '[appColorChanger]'
})
export class ColorChangerDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const colorStream = interval(1000).pipe(
      map(() => '#' + Math.floor(Math.random() * 16777215).toString(16))
    );

    colorStream.subscribe(color => {
      this.renderer.setStyle(this.el.nativeElement, 'color', color);
    });
  }
}

ここでは、interval(1000)を使用して1秒ごとに新しい値を発行するcolorStreamデータストリームを作成しています。この値はmap()関数を使用してランダムな色値に変換されます。最後に、colorStreamから新しい値があるたびに要素に色を適用するためにsubscribe()を使用します。

もう少し具体的な例を示しましょう。チャットアプリケーションを構築し、新しいメッセージがある場合にユーザーに通知を表示したいとします。この場合、ディレクティブを作成してDOMを操作(例えば、画面上に通知を表示する)し、RxJSを使用してメッセージのデータストリームを管理できます。

import { Directive, Renderer2, ElementRef } from '@angular/core';
import { of } from 'rxjs';
import { delay } from 'rxjs/operators';

@Directive({
  selector: '[appMessageNotifier]'
})
export class MessageNotifierDirective {
  constructor(private renderer: Renderer2, private el: ElementRef) {
    const messageStream = of('エム・トリンからの新しいメッセージです').pipe(delay(5000));

    messageStream.subscribe(message => {
      const div = this.renderer.createElement('div');
      const text = this.renderer.createText(message);

      this.renderer.addClass(div, 'notification');
      this.renderer.appendChild(div, text);
      this.renderer.appendChild(this.el.nativeElement, div);
    });
  }
}

上記の例では、messageStreamは5秒後に新しいメッセージを受信したことをシミュレートするObservableです。メッセージが到着すると、div要素を作成し、クラスnotificationを追加し、メッセージをこのdivに追加し、最後にdivをDOMに追加します。

4. ディレクティブとコンポーネントの比較

ディレクティブとコンポーネントは、Angularにおける2つの最も重要な概念と言えます。ただし、ディレクティブとコンポーネントには重要な違いがあります。

  • コンポーネントは、テンプレートを持つ特殊なタイプのディレクティブです。各コンポーネントには独自のビューがあり、子コンポーネントを含むことができます。

  • ディレクティブはテンプレートを持たず、適用されるDOM要素またはコンポーネントの動作を指定するだけです。

  • コンポーネントは通常、UIブロックを作成するために使用され、ディレクティブはDOM要素またはコンポーネントのカスタムな動作を作成するために使用されます。

Angularにおけるディレクティブについて共有したいことは以上です。では、記事の最後の部分に進んで、今まで学んだことを振り返りましょう。

5. 復習の質問

5.1 ディレクティブを使用する理由は何ですか?

ディレクティブを使用することで、DOM要素やコンポーネントのカスタムな動作を作成できます。これにより、Angularの機能を最大限に活用し、豊かで柔軟なアプリケーションを作成することができます。

5.2 カスタムディレクティブを使用するタイミングはいつですか?

Angularが提供する組み込みのディレクティブが要件を満たさない場合にカスタムディレクティブを使用するべきです。カスタムディレクティブを使用することで、任意のカスタムな動作を作成できます。

5.3 ElementRefではなくRenderer2を使用する理由は何ですか?

Renderer2を使用することで、DOMをより安全に操作することができます。ElementRefを直接使用することでセキュリティ上の脆弱性が生じる可能性があるため、Renderer2を使用するべきです。

5.4 ディレクティブとRxJSを一緒に使用する方法は?

ディレクティブ内でデータの更新を管理するためにRxJSを使用することができます。例えば、DOMイベントからObservableを作成し、そのObservableに対してディレクティブ内でsubscribeすることができます。

5.5 @Directiveを使用せずにディレクティブを作成できますか?

いいえ、@Directiveを使用せずにディレクティブを作成することはできません。@Directiveデコレータは、このディレクティブがディレクティブであることをAngularに知らせ、Angularがそのディレクティブを管理するために必要な情報を提供します。

この記事がAngularのディレクティブについての理解を深めるのに役立てば嬉しいです。引き続き、Angularに関するさらなる記事をお楽しみに!

Mình hy vọng bạn thích bài viết này và học thêm được điều gì đó mới.

Donate mình một ly cafe hoặc 1 cây bút bi để mình có thêm động lực cho ra nhiều bài viết hay và chất lượng hơn trong tương lai nhé. À mà nếu bạn có bất kỳ câu hỏi nào thì đừng ngại comment hoặc liên hệ mình qua: Zalo - 0374226770 hoặc Facebook. Mình xin cảm ơn.

Momo: NGUYỄN ANH TUẤN - 0374226770

TPBank: NGUYỄN ANH TUẤN - 0374226770 (hoặc 01681423001)

image.png


All rights reserved

Viblo
Hãy đăng ký một tài khoản Viblo để nhận được nhiều bài viết thú vị hơn.
Đăng kí