Hone logo
Hone
Problems

Implementing a contentChild Signal in Angular

Angular's signals provide a reactive way to manage component state. While @Input() and @Output() are common for data flow, directly accessing a child component's element (the DOM node) and reacting to its changes can be useful in specific scenarios like custom directives or advanced component interactions. This challenge asks you to create a custom directive that utilizes Angular's contentChild and signals to expose a signal that emits the child element.

Problem Description

You need to create an Angular directive called ContentChildSignal that wraps a child element and exposes a signal containing a reference to that element. The directive should:

  1. Use @ContentChild to query for the first element matching a specified selector within the directive's content.
  2. Convert the found element (which is a ElementRef) into a signal using signal(null).
  3. Update the signal whenever the child element changes (e.g., when the child is added or removed from the DOM). This requires using AfterViewInit and AfterViewChecked lifecycle hooks.
  4. The directive should accept an optional selector as an @Input(). If no selector is provided, it should default to *.
  5. The signal should emit null if no matching element is found.

Key Requirements:

  • The directive must be reusable and configurable via the selector input.
  • The signal must be reactive, updating whenever the child element changes.
  • The directive should handle cases where the child element is not present.
  • The directive should not introduce memory leaks.

Expected Behavior:

  • When the directive is initialized and a matching child element is found, the signal should emit a reference to that element.
  • If the child element is later added to the DOM, the signal should update to emit the new element.
  • If the child element is removed from the DOM, the signal should update to emit null.
  • If no matching child element is found, the signal should emit null.

Edge Cases to Consider:

  • No child element matching the selector is present.
  • The child element is dynamically added or removed.
  • The child element's properties change (though the signal only tracks the element reference itself, not its properties).
  • Multiple elements match the selector (the directive should only track the first matching element).

Examples

Example 1:

Input:
Template:
<my-directive #myDirective>
  <div id="myChild">Child Element</div>
</my-directive>

Component:
const childElementSignal = myDirective.childElementSignal();

Output:
childElementSignal.value === <div id="myChild">Child Element</div> (Initially)
childElementSignal.value updates to the same div if its properties change.
childElementSignal.value becomes null if the div is removed.

Explanation:
The directive finds the div with id "myChild" and the signal emits a reference to it.

Example 2:

Input:
Template:
<my-directive #myDirective selector="span">
  <span>This is a span</span>
  <div>This is a div</div>
</my-directive>

Component:
const spanElementSignal = myDirective.childElementSignal();

Output:
spanElementSignal.value === <span class="ng-star-inserted">This is a span</span> (Initially)
spanElementSignal.value becomes null if the span is removed.

Explanation:
The directive finds the first span element and the signal emits a reference to it.

Example 3: (Edge Case - No Matching Element)

Input:
Template:
<my-directive #myDirective selector="p">
  <span>This is a span</span>
</my-directive>

Component:
const paragraphElementSignal = myDirective.childElementSignal();

Output:
paragraphElementSignal.value === null (Initially)
paragraphElementSignal.value remains null.

Explanation:
No paragraph element is present within the directive's content, so the signal emits null.

Constraints

  • The directive must be compatible with Angular 16 or later.
  • The directive should not rely on any external libraries.
  • The signal should be updated efficiently to avoid performance bottlenecks. Avoid unnecessary DOM queries.
  • The selector must be a valid CSS selector string.

Notes

  • Consider using AfterViewInit and AfterViewChecked lifecycle hooks to detect changes in the child element.
  • Remember to handle the case where the child element is initially not present.
  • The signal should emit a reference to the element itself, not a copy.
  • Think about how to properly unsubscribe from any observables or subscriptions to prevent memory leaks. Signals themselves don't require explicit unsubscription, but any internal subscriptions do.
  • The ElementRef provides access to the underlying DOM element. Use it carefully and avoid directly manipulating the DOM unless necessary.
Loading editor...
typescript