Hone logo
Hone
Problems

Building Reusable UI with Angular and Web Components

This challenge focuses on leveraging Angular's capabilities to create and integrate custom, framework-agnostic Web Components. By building a simple, reusable counter component that can be consumed within an Angular application, you'll gain practical experience in bridging the gap between modern web standards and Angular's robust ecosystem.

Problem Description

Your task is to create a custom Web Component that functions as a simple counter. This component should allow users to increment and decrement a numerical value. Subsequently, you will integrate this Web Component into an Angular application.

Key Requirements:

  1. Web Component Creation:

    • Implement a custom element named custom-counter.
    • The custom-counter should display a numerical value.
    • It must expose an increment method that increases the displayed value by 1.
    • It must expose a decrement method that decreases the displayed value by 1.
    • It should accept an initial value attribute (defaulting to 0) to set the starting count.
    • The component should emit a custom event named valueChange whenever the counter's value is modified. This event should carry the new value as its detail.
  2. Angular Integration:

    • Create a new Angular project (or use an existing one).
    • Import and declare the custom Web Component within your Angular application.
    • Display the custom-counter component within an Angular component's template.
    • Bind an initial value to the custom-counter using an attribute.
    • Implement buttons or other UI elements in the Angular component to call the increment and decrement methods of the custom-counter instance.
    • Listen to the valueChange event emitted by the custom-counter and update a corresponding property in your Angular component's state.

Expected Behavior:

  • When the custom-counter is first rendered, it should display the initial value attribute.
  • Clicking an "Increment" button in the Angular application should call the increment method on the custom-counter, and the displayed value should increase by 1.
  • Clicking a "Decrement" button in the Angular application should call the decrement method on the custom-counter, and the displayed value should decrease by 1.
  • Every time the value changes, the valueChange event should be fired, and the Angular component should reflect this updated value.

Edge Cases to Consider:

  • Handling cases where the initial value attribute is not a valid number.
  • Ensuring the component behaves correctly when repeatedly incrementing or decrementing.

Examples

Example 1: Basic Web Component Usage

<!-- When rendered as a standalone HTML file -->
<custom-counter value="5"></custom-counter>
<button onclick="document.querySelector('custom-counter').increment()">Increment</button>
<button onclick="document.querySelector('custom-counter').decrement()">Decrement</button>

<script>
  // Assuming custom-counter is defined and registered
  const counter = document.querySelector('custom-counter');
  counter.addEventListener('valueChange', (event) => {
    console.log('Value changed:', event.detail);
  });
</script>

Output (in the console):

Value changed: 6  // After clicking increment
Value changed: 5  // After clicking decrement

Explanation: The custom-counter is initialized with value="5". Clicking the "Increment" button calls its increment method, changing the internal value to 6 and emitting valueChange with detail: 6. Clicking "Decrement" then changes it to 5 and emits valueChange with detail: 5.

Example 2: Angular Integration

Angular Component Template (app.component.html):

<h1>Angular App with Custom Counter</h1>
<custom-counter
  [attr.value]="initialCounterValue"
  (valueChange)="handleCounterChange($event)"
></custom-counter>

<p>Current Counter Value (from Angular): {{ currentAngularValue }}</p>

<button (click)="incrementCounter()">Increment</button>
<button (click)="decrementCounter()">Decrement</button>

Angular Component Logic (app.component.ts):

import { Component, ViewChild, ElementRef, AfterViewInit } from '@angular/core';

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.css']
})
export class AppComponent implements AfterViewInit {
  initialCounterValue: number = 10;
  currentAngularValue: number = this.initialCounterValue;

  // Assume customCounterElement is a reference to the custom-counter element
  // This would typically be obtained via @ViewChild
  private customCounterElement!: HTMLElement & { increment: () => void; decrement: () => void };

  @ViewChild('customCounter') customCounterRef!: ElementRef;

  ngAfterViewInit() {
    this.customCounterElement = this.customCounterRef.nativeElement;
  }

  handleCounterChange(event: CustomEvent) {
    this.currentAngularValue = event.detail;
  }

  incrementCounter() {
    this.customCounterElement.increment();
  }

  decrementCounter() {
    this.customCounterElement.decrement();
  }
}

Explanation: The Angular application binds an initial value to the custom-counter. It displays the counter's value and provides buttons to trigger its methods. The handleCounterChange method listens to the custom event and updates the Angular component's state.

Constraints

  • The Web Component must be implemented using plain JavaScript/TypeScript. No Angular-specific decorators or syntax should be used within the Web Component's core implementation itself.
  • The Angular integration should follow standard Angular practices.
  • The Web Component should be registered globally or imported and declared appropriately within Angular.
  • The initial value attribute should be a non-negative integer.

Notes

  • Consider using the customElements.define() API to register your Web Component.
  • For the Web Component, you'll likely need to manage its internal state and use attributeChangedCallback or similar mechanisms if you want to react to attribute changes dynamically after initial rendering. However, for this problem, binding the initial value and then controlling via methods is sufficient.
  • In Angular, you'll need to inform Angular that you are using a custom element. This can be done in your AppModule or the specific module where you're using it by adding it to schemas with CUSTOM_ELEMENTS_SCHEMA.
  • Accessing and calling methods on DOM elements from Angular typically involves ElementRef and potentially @ViewChild.
Loading editor...
typescript