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:
-
Web Component Creation:
- Implement a custom element named
custom-counter. - The
custom-countershould display a numerical value. - It must expose an
incrementmethod that increases the displayed value by 1. - It must expose a
decrementmethod that decreases the displayed value by 1. - It should accept an initial
valueattribute (defaulting to 0) to set the starting count. - The component should emit a custom event named
valueChangewhenever the counter's value is modified. This event should carry the new value as itsdetail.
- Implement a custom element named
-
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-countercomponent within an Angular component's template. - Bind an initial value to the
custom-counterusing an attribute. - Implement buttons or other UI elements in the Angular component to call the
incrementanddecrementmethods of thecustom-counterinstance. - Listen to the
valueChangeevent emitted by thecustom-counterand update a corresponding property in your Angular component's state.
Expected Behavior:
- When the
custom-counteris first rendered, it should display the initialvalueattribute. - Clicking an "Increment" button in the Angular application should call the
incrementmethod on thecustom-counter, and the displayed value should increase by 1. - Clicking a "Decrement" button in the Angular application should call the
decrementmethod on thecustom-counter, and the displayed value should decrease by 1. - Every time the value changes, the
valueChangeevent should be fired, and the Angular component should reflect this updated value.
Edge Cases to Consider:
- Handling cases where the initial
valueattribute 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
valueattribute 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
attributeChangedCallbackor 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
AppModuleor the specific module where you're using it by adding it toschemaswithCUSTOM_ELEMENTS_SCHEMA. - Accessing and calling methods on DOM elements from Angular typically involves
ElementRefand potentially@ViewChild.