Content Projection trong Angular
- 21-12-2023
- Toanngo92
- 0 Comments
Mục lục
Làm thế nào để sử dụng ng-content?
Bây giờ, hãy nghĩ về cách tận dụng sức mạnh của ng-content
trong Angular. Giả sử chúng ta có một component Toggle – một công cụ mà chúng ta muốn tái sử dụng để thu thập ý kiến từ người dùng. Cụ thể, chúng ta muốn đặt các câu hỏi dạng Yes/No và muốn mỗi câu hỏi có một nhãn riêng.
Vậy làm thế nào để làm điều này mà không phải thêm các tham số vào component? Đây là nơi mà ng-content
tỏ ra hữu ích. Thay vì chỉ định rõ câu hỏi trong component, chúng ta có thể chèn nội dung trực tiếp từ bên ngoài vào component mà không cần sửa đổi component đó.
Trong component Toggle, chúng ta có một cấu trúc HTML như sau:
<div class="toggle-wrapper" [class.checked]="checked" tabindex="0" (click)="toggle()">
<div class="toggle"></div>
</div>
<div class="toggle-label">
<ng-content></ng-content>
</div>
Ở đây, ng-content
chính là chỗ mà nội dung từ bên ngoài sẽ được chèn vào.
Ở phía component cha, chúng ta có thể sử dụng component Toggle như sau để tạo các câu hỏi khác nhau:
<app-toggle [(checked)]="questions.question1">
<span>Would you recommend our service? Yes/No</span>
</app-toggle>
<app-toggle [(checked)]="questions.question2">
<span>Did our product meet your expectations? Yes/No</span>
</app-toggle>
Nhìn vào đây, mỗi lần chúng ta sử dụng app-toggle
, chúng ta chèn một câu hỏi cụ thể thông qua thẻ span
. Điều này giúp chúng ta tạo ra các câu hỏi khác nhau một cách dễ dàng mà không cần phải chỉnh sửa trực tiếp vào component Toggle.
Sử dụng multiple ng-content được không?
Nếu bạn muốn sử dụng nhiều ng-content
trong một template, có phải khi bạn đặt chúng nhiều lần thì tất cả sẽ hiển thị ra hay không? Hãy xem xét ví dụ sau:
Trong một component, chúng ta thử sử dụng ng-content
hai lần:
<div class="toggle-label">
<div>content 1</div>
<ng-content></ng-content>
</div>
<div class="toggle-label">
<div>content 2</div>
<ng-content></ng-content>
</div>
Nếu chúng ta nhìn vào kết quả render, chỉ thấy content 2
được hiển thị với nhãn mà chúng ta truyền vào. Điều này có nghĩa là việc sử dụng nhiều ng-content
có thể không cho kết quả như chúng ta mong đợi. Tại sao lại như vậy?
Điều này hoàn toàn bình thường, giống như các thẻ HTML khác như header, chúng ta chỉ có một vùng để hiển thị. Vì vậy, với ng-content
dạng này, chỉ nên có một tag duy nhất được sử dụng.
Khi bạn đặt nhiều ng-content
, Angular sẽ hiểu rằng nội dung của tất cả chúng sẽ được thay thế bởi nội dung truyền vào cuối cùng. Điều này có thể dẫn đến việc mất đi nội dung của ng-content
trước đó và chỉ hiển thị nội dung của ng-content
cuối cùng. Điều này rõ ràng cho thấy tại sao chỉ có content 2
được hiển thị trong trường hợp này.
ng-content và selector trong angular
ng-content
và selector trong Angular mang đến khả năng kiểm soát rất linh hoạt, giống như khi bạn sắp xếp các phần tử trong thẻ table HTML. Bất kể bạn đặt thead, tbody hay tfoot ở đâu, chúng sẽ hiển thị theo thứ tự đúng đắn: thead trước, sau đó tbody, và cuối cùng là tfoot. Angular cũng cho phép chúng ta điều chỉnh thứ tự hiển thị của nội dung thông qua việc kết hợp ng-content
với các selector.
Nhưng khi nói về việc dùng nhiều ng-content
, có điều cần lưu ý. Không phải là khi chúng ta sử dụng ng-content
để chọn tất cả các phần tử.
Selector có thể có nhiều dạng khác nhau:
- Selector theo thẻ:
<ng-content select="some-component-selector-or-html-tag"></ng-content>
- Selector theo CSS Class:
<ng-content select=".some-class"></ng-content>
- Selector theo thuộc tính:
<ng-content select="[some-attr]"></ng-content>
- Kết hợp nhiều selector:
<ng-content select="some-component-selector-or-html-tag[some-attr]"></ng-content>
Ví dụ với component Toggle:
<header>
<ng-content select=".toogle-header"></ng-content>
</header>
<div class="toggle-wrapper" [class.checked]="checked" tabindex="0" (click)="toggle()">
<div class="toggle"></div>
</div>
<div class="toggle-label">
<ng-content select="label"></ng-content>
</div>
<div class="toggle-content">
<ng-content></ng-content>
</div>
Trong file app.component.html, ngay cả khi chúng ta đặt các phần tử không theo thứ tự, Toggle component vẫn hiển thị như chúng ta mong muốn.
<app-toggle [(checked)]="questions.question1">
<h3 class="toggle-header">Header 1</h3>
<label>Question 1</label>
<span>Some paragraph</span>
</app-toggle>
<app-toggle [(checked)]="questions.question2">
<h3 class="toggle-header">Header 2</h3>
<span>Some paragraph 2</span>
<label>Question 2</label>
</app-toggle>
Tuy nhiên, khi sử dụng selector, nếu có nhiều phần tử thỏa mãn điều kiện của selector, ng-content
sẽ nhận tất cả các phần tử đó. Điều này cần được lưu ý để tránh hiển thị không mong muốn.
ng-content và ngProjectAs trong angular
Ng-content
và ngProjectAs
trong Angular có thể đem lại sự linh hoạt cho việc truyền nội dung vào các component, nhưng đôi khi, khi chúng ta muốn sử dụng một selector cụ thể nhưng không thể làm được điều đó, có cách nào để giải quyết vấn đề này không?
Giả sử bạn muốn component Toggle nhận vào một component có selector là app-label
, nhưng người khác có thể muốn sử dụng một loại label khác hoặc thậm chí là một thẻ label thông thường từ HTML, và có thể component app-label
không nằm trực tiếp bên trong app-toggle
mà nó được bao bọc bởi một thẻ div chẳng hạn.
Ở đây, selector của chúng ta không thể nhận dạng đúng phần tử cần truyền vào. Có một cách để báo cho Angular biết rằng chúng ta muốn chuyển nội dung này vào component như thế nào, đó là ngProjectAs
. Đây là cách để chỉ định một cách rõ ràng về cách thức xử lý nội dung được chuyển vào.
Trong file app.component.html:
<app-toggle [(checked)]="questions.question1">
<h3 class="toogle-header">Header 1</h3>
<label ngProjectAs="app-label">Question 1</label>
<span>Some paragraph</span>
</app-toggle>
Bằng cách sử dụng ngProjectAs="app-label"
trong label, chúng ta báo cho Angular biết rằng phần nội dung này nên được xử lý như là một app-label
, ngay cả khi nó không phải là một component app-label
trực tiếp và có thể bị bao bọc bởi các phần tử khác.
Rất nhiều thư viện như ngx-dropzone cũng sử dụng kỹ thuật này để cho phép người dùng tùy chỉnh và sử dụng các thành phần một cách linh hoạt hơn.