Dynamic List Renderer with Structural Directive
This challenge asks you to create a custom structural directive in Angular that dynamically renders a list of items based on an input array. Structural directives control the DOM by adding, removing, or manipulating elements. This is a fundamental concept for building reusable components and managing complex UI structures.
Problem Description
You need to build an Angular structural directive called ListRenderer that takes an array of data as input and renders each item in the array as an <li> element within an unordered list (<ul>). The directive should dynamically add or remove <li> elements from the <ul> based on changes to the input array. The directive should also accept an optional itemTemplate input, which is a template to be used for rendering each item. If no itemTemplate is provided, a simple text representation of the item should be used.
Key Requirements:
- The directive must accept an array of data as input (
@Input() data: any[]). - The directive must render each item in the array as an
<li>element within a<ul>element. - The directive must dynamically update the list when the input array changes.
- The directive should accept an optional template input (
@Input() itemTemplate: TemplateRef<any>). - If
itemTemplateis provided, it should be used to render each item. - If
itemTemplateis not provided, the item's string representation (usingtoString()) should be displayed within the<li>.
Expected Behavior:
When the data input changes, the directive should:
- Remove any existing
<li>elements that are no longer in the newdataarray. - Add new
<li>elements for any items that are now in thedataarray but were not previously rendered. - Update the content of existing
<li>elements if the item's data has changed.
Edge Cases to Consider:
- Empty input array: The
<ul>should be empty. nullorundefinedinput array: The<ul>should be empty.- Items in the array that are
nullorundefined: Handle these gracefully (e.g., display a placeholder or skip rendering). itemTemplateis provided but invalid: Handle the error gracefully (e.g., fall back to the default rendering).
Examples
Example 1:
Input: data = [{name: 'Alice'}, {name: 'Bob'}]
Output:
<ul>
<li><div class="item">Alice</div></li>
<li><div class="item">Bob</div></li>
</ul>
Explanation: The directive renders two list items, each displaying the 'name' property of the corresponding object. The default rendering is used as no template is provided.
Example 2:
Input: data = [{name: 'Alice'}, {name: 'Bob'}, {name: 'Charlie'}]
itemTemplate: <ng-template #itemTemplate let-item="item">
<div class="custom-item">
<strong>{{item.name}}</strong> - {{item.age | default: 'N/A'}}
</div>
</ng-template>
Output:
<ul>
<li><div class="custom-item"><strong>Alice</strong> - N/A</div></li>
<li><div class="custom-item"><strong>Bob</strong> - N/A</div></li>
<li><div class="custom-item"><strong>Charlie</strong> - N/A</div></li>
</ul>
Explanation: The directive renders three list items, each using the provided `itemTemplate` to display the 'name' and 'age' properties. The `default` pipe handles cases where 'age' is undefined.
Example 3:
Input: data = []
Output:
<ul></ul>
Explanation: The directive renders an empty unordered list because the input array is empty.
Constraints
- The directive must be implemented using Angular's structural directive API (
@Directive,@Input,ViewContainerRef,TemplateRef). - The directive should be performant and avoid unnecessary DOM manipulations.
- The directive should be reusable and adaptable to different data structures.
- The directive should handle potential errors gracefully.
- The solution must be written in TypeScript.
Notes
- Consider using
ViewContainerRef.clear()to remove all existing elements before re-rendering the list. - Use
ViewContainerRef.createEmbeddedView()to create views from theitemTemplate. - Think about how to efficiently compare items in the input array to determine if they need to be added, removed, or updated. Simple array comparison might be sufficient for smaller datasets, but consider more optimized approaches for larger datasets.
- The
defaultpipe is a placeholder. You can implement it or use a similar approach to handle missing data. The key is to demonstrate handling of potentially missing data within the template.