Lesson 15-Angular Elements

Converting Angular Components to Web Components

Web Components are a set of W3C standards enabling developers to create reusable custom HTML elements. Angular Elements is an experimental feature that allows you to export Angular components as Web Components, making them usable in non-Angular environments, such as plain HTML or frameworks like React and Vue.

How Angular Elements Work

Angular Elements leverages Web Components’ core APIs, including customElements.define() and Shadow DOM. When exporting an Angular component as a Web Component, Angular wraps it with a Shadow DOM, isolating styles and logic to create an independent, reusable component.

Preparing an Angular Project

First, ensure you have an Angular project. If not, create one using Angular CLI:

ng new my-element-app
cd my-element-app

Creating an Angular Component

Generate a new component in the src/app directory:

ng generate component my-element

This creates the following files:

  • my-element.component.ts
  • my-element.component.html
  • my-element.component.css
  • my-element.component.spec.ts

Writing the Angular Component

Modify my-element.component.ts to make it exportable as a Web Component:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-my-element',
  template: `
    <p>Hello, {{ name }}!</p>
    <button (click)="onClick()">Click me!</button>
  `,
  styles: [`
    p {
      color: red;
    }
  `]
})
export class MyElementComponent {
  @Input() name: string;
  @Output() clicked = new EventEmitter<void>();

  onClick() {
    this.clicked.emit();
  }
}

Exporting the Angular Component as a Web Component

To export the component as a Web Component, use the @angular/elements package. Install it:

npm install @angular/elements --save

In src/app/app.module.ts, import AngularElementsModule and register your component:

import { NgModule, Injector } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { enableProdMode } from '@angular/core';
import { MyElementComponent } from './my-element/my-element.component';
import { AppComponent } from './app.component';
import { createCustomElement } from '@angular/elements';

@NgModule({
  imports: [
    BrowserModule
  ],
  declarations: [
    AppComponent,
    MyElementComponent
  ],
  entryComponents: [MyElementComponent], // Add your component here
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(private injector: Injector) {
    const myElement = createCustomElement(MyElementComponent, { injector });
    customElements.define('my-element', myElement);
  }

  ngDoBootstrap() {}
}

Using the Web Component in a Non-Angular Environment

You can now use the <my-element> tag in non-Angular environments, such as a plain HTML file:

<!DOCTYPE html>
<html>
<head>
  <script src="path/to/built/app.js"></script>
</head>
<body>
  <my-element name="World"></my-element>

  <script>
    document.querySelector('my-element').addEventListener('clicked', () => {
      console.log('Clicked!');
    });
  </script>
</body>
</html>

Handling Cross-Origin Issues

If your application runs on different domains or subdomains, consider Cross-Origin Resource Sharing (CORS) policies. Ensure your server is configured to allow cross-origin requests.

Using Angular Components in Non-Angular Applications

Using Angular components in non-Angular environments typically involves exporting them as Web Components and integrating these custom elements into HTML or other frameworks.

Creating an Angular Component

Assume you have an Angular project. Generate a new component using Angular CLI:

ng generate component my-angular-component

This generates the following file structure:

src/app/my-angular-component/
├── my-angular-component.component.css
├── my-angular-component.component.html
├── my-angular-component.component.spec.ts
└── my-angular-component.component.ts

Modifying the Angular Component

Update my-angular-component.component.ts to ensure it’s exportable as a Web Component:

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

@Component({
  selector: 'app-my-angular-component',
  templateUrl: './my-angular-component.component.html',
  styleUrls: ['./my-angular-component.component.css']
})
export class MyAngularComponent {
  message = 'Hello from Angular!';
}

Exporting as a Web Component

Use the @angular/elements module to convert the Angular component into a Web Component. Ensure it’s installed:

npm install @angular/elements

In your Angular module, import AngularElementsModule and add the component to entryComponents:

// app.module.ts
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent } from './app.component';
import { MyAngularComponent } from './my-angular-component/my-angular-component.component';
import { AngularElementsModule } from '@angular/elements';

@NgModule({
  declarations: [
    AppComponent,
    MyAngularComponent
  ],
  imports: [
    BrowserModule,
    AngularElementsModule
  ],
  entryComponents: [MyAngularComponent],
  bootstrap: [AppComponent]
})
export class AppModule {}

Registering the Web Component

In main.ts, use createCustomElement to register the component:

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app/app.module';
import { MyAngularComponent } from './app/my-angular-component/my-angular-component.component';
import { createCustomElement } from '@angular/elements';
import { Injector } from '@angular/core';

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.error(err));

const MyAngularComponentElement = createCustomElement(MyAngularComponent, { injector: Injector.create({ providers: [] }) });
customElements.define('my-angular-component', MyAngularComponentElement);

Building the Angular Application

Before using the component in a non-Angular environment, build the Angular application:

ng build --prod

This generates a dist directory containing the compiled Angular application.

Using the Web Component in a Non-Angular Environment

You can now use the <my-angular-component> tag in any HTML file:

<!DOCTYPE html>
<html>
<head>
  <script type="module" src="/path/to/dist/runtime.js"></script>
  <script type="module" src="/path/to/dist/polyfills.js"></script>
  <script type="module" src="/path/to/dist/styles.js"></script>
  <script type="module" src="/path/to/dist/vendor.js"></script>
  <script type="module" src="/path/to/dist/main.js"></script>
</head>
<body>
  <my-angular-component></my-angular-component>
</body>
</html>

Ensure all necessary scripts (runtime, polyfills, styles, vendor, and main) from the dist directory are loaded.

Handling Events and Properties

You can handle events and set properties in HTML. For example:

<my-angular-component message="Welcome to the world of Web Components!"></my-angular-component>

In the Angular component, use @Input and @Output decorators to define properties and events:

import { Component, Input, Output, EventEmitter } from '@angular/core';

@Component({
  selector: 'app-my-angular-component',
  templateUrl: './my-angular-component.component.html',
  styleUrls: ['./my-angular-component.component.css']
})
export class MyAngularComponent {
  @Input() message: string;
  @Output() messageChanged = new EventEmitter<string>();

  onMessageChange(newMessage: string) {
    this.message = newMessage;
    this.messageChanged.emit(newMessage);
  }
}

Bind events in HTML:

<my-angular-component message="Initial message" (messageChanged)="handleMessageChange($event)"></my-angular-component>

Handling Events in JavaScript:

document.querySelector('my-angular-component').addEventListener('messageChanged', function(event) {
  console.log('Message changed:', event.detail);
});

By using Angular Elements, you can convert Angular components into Web Components for use in non-Angular environments. This enhances component portability and reusability, allowing sharing across frameworks or libraries. Following these steps, you can embed Angular components in any HTML page or even in React or Vue applications. This flexibility not only increases component versatility but also boosts development efficiency by eliminating the need to reimplement components for different frameworks.

Share your love