Hone logo
Hone
Problems

Recreate the ngFor Directive in Angular

This challenge asks you to build a custom directive that mimics the behavior of Angular's built-in *ngFor directive. This is a fundamental exercise for understanding how structural directives work in Angular, specifically how they manipulate the DOM and manage repeating elements.

Problem Description

Your task is to create a custom Angular directive, let's call it [appFor], that iterates over a collection and renders a template for each item in the collection. This directive should behave similarly to the *ngFor directive, accepting an array and an optional template for rendering.

Key Requirements:

  1. Iteration: The directive must iterate over an input array.
  2. Template Rendering: For each item in the array, the directive should render a provided template.
  3. Dynamic Updates: If the input array changes (items added, removed, or reordered), the directive should update the DOM accordingly.
  4. Context Provision: Each rendered template instance should have access to the current item being iterated over.
  5. "let" Syntax Emulation: Emulate the let item of collection syntax for accessing the current item.

Expected Behavior:

When applied to an element with a template, the directive should repeat that template for each item in the provided collection.

Edge Cases to Consider:

  • An empty input array should result in no template instances being rendered.
  • The input collection might change after initialization.
  • The directive should be able to handle different data types within the array.

Examples

Example 1:

Component Template:

<div *appFor="let user of users">
  {{ user.name }} - {{ user.age }}
</div>

Component Class:

import { Component } from '@angular/core';

@Component({
  selector: 'app-user-list',
  template: `
    <div *appFor="let user of users">
      {{ user.name }} - {{ user.age }}
    </div>
  `
})
export class UserListComponent {
  users = [
    { name: 'Alice', age: 30 },
    { name: 'Bob', age: 25 }
  ];
}

Expected Output (rendered DOM):

<div>
  Alice - 30
</div>
<div>
  Bob - 25
</div>

Explanation: The *appFor directive iterates over the users array. For each user object, it renders the div content, injecting the user.name and user.age properties.

Example 2:

Component Template:

<ul>
  <li *appFor="let num of numbers; let i = index">
    {{ i + 1 }}. {{ num }}
  </li>
</ul>

Component Class:

import { Component } from '@angular/core';

@Component({
  selector: 'app-number-list',
  template: `
    <ul>
      <li *appFor="let num of numbers; let i = index">
        {{ i + 1 }}. {{ num }}
      </li>
    </ul>
  `
})
export class NumberListComponent {
  numbers = [10, 20, 30, 40];
}

Expected Output (rendered DOM):

<ul>
  <li>
    1. 10
  </li>
  <li>
    2. 20
  </li>
  <li>
    3. 30
  </li>
  <li>
    4. 40
  </li>
</ul>

Explanation: This example demonstrates rendering an index. The let i = index part is a specific feature of *ngFor that you should also aim to emulate. The directive iterates over numbers, and for each num, it renders an li displaying its sequential index (starting from 1) and its value.

Example 3 (Edge Case):

Component Template:

<p *appFor="let item of emptyList">
  This should not be rendered.
</p>

Component Class:

import { Component } from '@angular/core';

@Component({
  selector: 'app-empty-list',
  template: `
    <p *appFor="let item of emptyList">
      This should not be rendered.
    </p>
  `
})
export class EmptyListComponent {
  emptyList: any[] = [];
}

Expected Output (rendered DOM): (Nothing rendered within the app-empty-list component's template)

Explanation: When the input array emptyList is empty, the directive correctly renders no elements.

Constraints

  • The directive must be implemented as a structural directive.
  • Your directive should be named AppForDirective.
  • The input expression format should be parsable by your directive to extract the collection and the variable name for the item (e.g., let item of collection).
  • You should handle basic DOM manipulation using ViewContainerRef and TemplateRef.
  • Performance should be considered, especially when dealing with large lists, although an initial implementation focusing on correctness is prioritized.

Notes

  • Remember that * syntax is syntactic sugar for [appFor] and passing a TemplateRef. Your directive will need to accept a TemplateRef.
  • ViewContainerRef is essential for inserting and removing embedded views (your template instances).
  • Consider how you will handle changes to the input collection. IterableDiffers and KeyValueDiffers in Angular are powerful tools for this, but for a first pass, you might consider a simpler approach like detecting changes directly.
  • Emulating the let i = index requires passing additional context to the embedded view.
Loading editor...
typescript