Hone logo
Hone
Problems

Custom ngStyle Directive Implementation

Angular's built-in ngStyle directive is a powerful tool for dynamically applying inline styles to HTML elements based on component logic. This challenge asks you to reimplement this functionality as a custom directive, solidifying your understanding of attribute directives and DOM manipulation in Angular. Successfully implementing this will allow you to create more dynamic and responsive UIs.

Problem Description

Your task is to create a custom Angular attribute directive, let's call it [appStyle], that mimics the behavior of the [ngStyle] directive. This directive should accept an object where keys are CSS property names and values are their corresponding CSS values. The directive should then apply these styles to the host HTML element.

Key Requirements:

  1. Input Binding: The directive must accept an input that is an object. This object will define the styles to be applied.
  2. Dynamic Updates: When the input object changes (e.g., due to component property updates), the directive should update the element's inline styles accordingly.
  3. Style Application: The directive needs to apply styles to the host element by manipulating its style property.
  4. CSS Property and Value Handling: The directive should correctly handle various CSS property names (e.g., backgroundColor, fontSize, margin-left) and their corresponding values (strings, numbers, etc.).

Expected Behavior:

When the [appStyle] directive is applied to an element with a bound object:

<div [appStyle]="myStylesObject">...</div>

And myStylesObject in the component looks like this:

myStylesObject = {
  'background-color': 'lightblue',
  'font-size': '16px',
  'padding': '10px'
};

The div element should have the following inline styles applied:

<div style="background-color: lightblue; font-size: 16px; padding: 10px;">...</div>

If myStylesObject is updated, the styles on the div should also update.

Edge Cases to Consider:

  • Empty Style Object: What happens if the input object is empty or null? The directive should gracefully handle this, ideally by removing any previously applied styles.
  • Invalid CSS Values: While not strictly required to validate CSS values, the directive should pass them through as is. The browser will handle invalid values.
  • Updating Individual Properties: If the object changes from {'color': 'red'} to {'color': 'blue', 'fontSize': '20px'}, both the color and font size should be updated. If a style is removed from the object, it should be removed from the element's inline styles.

Examples

Example 1: Applying basic styles.

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

@Component({
  selector: 'app-root',
  template: `
    <div [appStyle]="styles">Hello World</div>
  `
})
export class AppComponent {
  styles = {
    'color': 'blue',
    'font-weight': 'bold'
  };
}

Expected Output (rendered HTML for the div):

<div style="color: blue; font-weight: bold;">Hello World</div>

Explanation: The [appStyle] directive receives the styles object and applies color: blue and font-weight: bold as inline styles to the div.

Example 2: Dynamically updating styles.

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

@Component({
  selector: 'app-root',
  template: `
    <button (click)="changeStyles()">Change Styles</button>
    <div [appStyle]="dynamicStyles">This div's styles will change.</div>
  `
})
export class AppComponent {
  dynamicStyles = {
    'background-color': 'yellow',
    'border': '1px solid black'
  };

  changeStyles() {
    this.dynamicStyles = {
      ...this.dynamicStyles, // Preserve existing styles
      'background-color': 'lightgreen',
      'padding': '15px'
    };
  }
}

Expected Output (initially):

<div style="background-color: yellow; border: 1px solid black;">This div's styles will change.</div>

Expected Output (after clicking the button):

<div style="background-color: lightgreen; border: 1px solid black; padding: 15px;">This div's styles will change.</div>

Explanation: Initially, the div has background-color: yellow and border: 1px solid black. After clicking the button, the changeStyles method updates dynamicStyles, setting the background-color to lightgreen and adding a padding of 15px, while retaining the border.

Example 3: Handling removal of styles.

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

@Component({
  selector: 'app-root',
  template: `
    <button (click)="removePadding()">Remove Padding</button>
    <div [appStyle]="stylesWithPadding">Content</div>
  `
})
export class AppComponent {
  stylesWithPadding = {
    'color': 'purple',
    'padding': '20px'
  };

  removePadding() {
    this.stylesWithPadding = {
      'color': 'purple' // Padding is removed from the object
    };
  }
}

Expected Output (initially):

<div style="color: purple; padding: 20px;">Content</div>

Expected Output (after clicking the button):

<div style="color: purple;">Content</div>

Explanation: Initially, the div has color: purple and padding: 20px. When removePadding is called, the padding property is no longer present in the stylesWithPadding object. The directive should remove the padding style from the element's inline styles.

Constraints

  • The directive must be an attribute directive.
  • The input to the directive must be an object of type {[key: string]: string | number | null | undefined}.
  • The directive should leverage Angular's change detection mechanism to update styles when the input object changes.
  • Do not use any third-party libraries for DOM manipulation.

Notes

  • You'll need to inject ElementRef and potentially Renderer2 into your directive to access and manipulate the host element's styles.
  • Consider using the ngOnChanges lifecycle hook to detect changes in the input object.
  • When an object is passed, CSS property names might be camelCased (e.g., backgroundColor) or kebab-cased (e.g., background-color). Your directive should be able to handle both representations if the input object uses camelCase for consistency. However, for this challenge, assume the input object will use kebab-cased CSS property names or camelCased names that map directly to style properties. The Renderer2's setStyle method is generally robust in handling property names.
  • When updating styles, it's important to remove styles that are no longer present in the input object to avoid orphaned styles.
Loading editor...
typescript