Please note, that Angular has 2 modes using components:
- standalone
- module based
In Angular 19, which is expected to be released in November 2024, the standalone: true
flag will become the default for all components, directives, and pipes.
Standalone components were introduced in Angular 14 as a developer preview in June 2022.
Install dap-design-system
inside project folder.
npm i dap-design-system
In angular.json
add light.theme.css
(just an example, some parts omitted).
"angular-project-name": {
"architect": {
"build": {
"options": {
"styles": [
"src/styles.scss",
"dap-design-system/dist/light.theme.css"
],
}
}
}
}
In main.ts
add import 'dap-design-system/dist/dds.js';
import 'dap-design-system/dist/dds.js';
import { bootstrapApplication } from '@angular/platform-browser';
import { appConfig } from './app/app.config';
import { AppComponent } from './app/app.component';
bootstrapApplication(AppComponent, appConfig)
.catch((err) => console.error(err));
In app.component.ts
add:
CUSTOM_ELEMENTS_SCHEMA
import and schema- also add
onDDSInputChanged
function.
import { Component, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { RouterOutlet } from '@angular/router';
@Component({
selector: 'app-root',
standalone: true,
schemas: [CUSTOM_ELEMENTS_SCHEMA],
imports: [RouterOutlet],
templateUrl: './app.component.html',
styleUrl: './app.component.scss',
})
export class AppComponent {
title = 'angular-project';
onDDSInputChanged(event: CustomEvent) {
console.log(event.detail.value);
}
}
In app.component.html
add a form and dap-ds-input
with (dds-change)="onDDSInputChanged($event)"
<h1>App Component</h1>
<form id="input-form">
<dap-ds-input
(dds-change)="onDDSInputChanged($event)"
id="text-input"
name="text-input"
required
label="Required"
description="Description"
helperText="Validate for required"
tooltip="Tooltip content"
feedbackType="negative" value=""></dap-ds-input>
</form>
Install dap-design-system
inside project folder.
npm i dap-design-system
In angular.json
add light.theme.css
(just an example, some parts omitted).
"angular-project-name": {
"architect": {
"build": {
"options": {
"styles": [
"src/styles.scss",
"dap-design-system/dist/light.theme.css"
],
}
}
}
}
In main.ts
add import 'dap-design-system/dist/dds.js';
import 'dap-design-system/dist/dds.js';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
platformBrowserDynamic().bootstrapModule(AppModule)
.catch(err => console.error(err));
In app.module.ts
add CUSTOM_ELEMENTS_SCHEMA
.
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
@NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule
],
providers: [],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule { }
In app.component.ts
add custom event function: onDDSInputChanged
import { Component } from '@angular/core';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
title = 'angular-15';
onDDSInputChanged(event: CustomEvent) {
console.log(event.detail.value);
}
}
In app.component.html
add dap-ds-input
<h1>App Component</h1>
<form id="input-form">
<dap-ds-input
(dds-change)="onDDSInputChanged($event)"
id="text-input"
name="text-input"
required
label="Required"
description="Description"
helperText="Validate for required"
tooltip="Tooltip content"
feedbackType="negative" value=""></dap-ds-input>
</form>
<router-outlet></router-outlet>
Web components offer the benefit of using components that work anywhere, in any front end framework or technology. While web components work with Angular's template API, they don't have access to Angular's helpful form APIs such as custom validation and form state management. Using Angular Directives with ControlValueAccessor
, we can level up our web components to work seamlessly with Angular Forms.
Web components primarily communicate via properties and custom events, just like Angular Input
and Output
s. However, when used with Angular forms, they don't work as well as custom Angular form controls.
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h2>Web Component</h2>
<p>{{count}}</p>
<dap-ds-input
[value]="count"
(dds-change)="count = $event.detail.value">
</dap-ds-input>
`
})
export class AppComponent {
count = 5;
}
To integrate web components with Angular Forms, we can create a directive that implements ControlValueAccessor
. This allows us to use the same custom element selector as our web component and attach Angular-specific API logic.
import {
Directive,
forwardRef,
HostBinding,
Input,
Output,
EventEmitter,
ElementRef,
HostListener
} from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
selector: 'dap-ds-input',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => DapInputDirective),
multi: true
}
]
})
export class DapInputDirective implements ControlValueAccessor {
onChange: any = () => {};
onTouched: any = () => {};
private _value: string;
get value() {
return this._value;
}
set value(val) {
if (val !== this._value) {
this._value = val;
this.onChange(this._value);
this.onTouched();
this.elementRef.nativeElement.value = val;
}
}
constructor(private elementRef: ElementRef) { }
@HostListener('dds-change', ['$event.detail.value'])
listenForValueChange(value) {
this.value = value;
}
writeValue(value) {
if (value) {
this.value = value;
}
}
registerOnChange(fn) {
this.onChange = fn;
}
registerOnTouched(fn) {
this.onTouched = fn;
}
}
Once the directive is implemented, you can use the web component with Angular's Reactive Forms API:
import { Component } from '@angular/core';
import { FormControl, Validators } from '@angular/forms';
@Component({
selector: 'my-app',
template: `
<h2>Angular Reactive Forms with Web Component</h2>
<p>Value: {{inputControl.value}}</p>
<p>Valid: {{inputControl.valid}}</p>
<dap-ds-input [formControl]="inputControl"></dap-ds-input>
`
})
export class AppComponent {
inputControl = new FormControl('', Validators.required);
}
You can also use the web component with Angular's Template-Driven Forms using ngModel
:
import { Component } from '@angular/core';
@Component({
selector: 'my-app',
template: `
<h2>Angular Template Forms with Web Component</h2>
<p>Value: {{inputValue}}</p>
<dap-ds-input [(ngModel)]="inputValue"></dap-ds-input>
`
})
export class AppComponent {
inputValue = '';
}
For module-based Angular applications, make sure to include the directive in your module declarations:
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { ReactiveFormsModule, FormsModule } from '@angular/forms';
import { AppComponent } from './app.component';
import { DapInputDirective } from './dap-input.directive';
@NgModule({
imports: [BrowserModule, ReactiveFormsModule, FormsModule],
declarations: [AppComponent, DapInputDirective],
bootstrap: [AppComponent],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class AppModule {}
By implementing ControlValueAccessor
, your web components gain:
- Full Angular Forms API support: Access to validation, form state management, and reactive forms
- Custom validation: Apply Angular validators to web component values
- Form state tracking: Automatic tracking of touched, dirty, and valid states
- Reactive forms integration: Use with
FormControl
,FormGroup
, andFormArray
- Template-driven forms: Support for
ngModel
and two-way binding - Framework-level integration: Maintain the excellent developer experience of Angular's form APIs
This approach gives you the best of both worlds: the reusability of web components and the powerful form management capabilities of Angular.
Reference: Using Web Components in Angular Forms by Cory Rylan