Creating Web Components with Angular

John Au-Yeung - Jan 23 '21 - - Dev Community

Check out my books on Amazon at https://www.amazon.com/John-Au-Yeung/e/B08FT5NT62

Subscribe to my email list now at http://jauyeung.net/subscribe/

Angular is a popular front-end framework made by Google. Like other popular front-end frameworks, it uses a component-based architecture to structure apps.

In this article, we’ll look at how to create Angular Elements, which are packaged as Web Components.

Angular Elements

Web Components are supported by most browsers like Chrome, Firefox, Opera or Safari.

We can transform Angular components to Web Components to make all the Angular infrastructure available to the browser.

Features like data-binding and other Angular functionalities are mapped to their HTML equivalents.

Creating and Using Custom Elements

We can create a Web Component by creating an Angular component, then building it into a Web Component.

To create a Web Component with Angular, we have to do a few things.

First, we create a component to build into Web Components. Then we have to set the component we created as the entry point.

Then we can add it to the DOM.

We’ll make a custom component to get a joke. To do this, we first run:

ng g component customJoke  
ng g service joke
Enter fullscreen mode Exit fullscreen mode

to create our component and service to get our joke and display it.

Then we run:

ng add @angular/element
Enter fullscreen mode Exit fullscreen mode

to add the Angular Element files to create our Web Component.

Then injoke.service.ts , we add:

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

@Injectable({  
  providedIn: 'root'  
})  
export class JokeService { 
  constructor() { } 

  async getJokeById(id: number) {  
    const response = await fetch(`http://api.icndb.com/jokes/${id}`)  
    const joke = await response.json();  
    return joke;  
  }  
}
Enter fullscreen mode Exit fullscreen mode

The code above gets a joke from the Chuck Norris API by ID.

Next, we write our component code as follows:

app.module.ts :

import { BrowserModule } from '@angular/platform-browser';  
import { NgModule, Injector } from '@angular/core';  
import { AppComponent } from './app.component';  
import { CustomJokeComponent } from './custom-joke/custom-joke.component';  
import { JokeService } from './joke.service';

@NgModule({  
  declarations: [  
    CustomJokeComponent,  
    AppComponent  
  ],  
  imports: [  
    BrowserModule,  
  ],  
  providers: [JokeService],  
  bootstrap: [AppComponent],  
  entryComponents: [CustomJokeComponent]  
})  
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

In AppModule , we add CustomJokeComponent to entryComponents so that it’ll be the entry point component instead of AppComponent .

It’ll load when custom-joke element is created.

app.component.ts :

import { Component, Injector } from '@angular/core';  
import { createCustomElement, WithProperties, NgElement } from '@angular/elements';  
import { CustomJokeComponent } from './custom-joke/custom-joke.component';

@Component({  
  selector: 'app-root',  
  template: ''  
})  
export class AppComponent {  
  constructor(injector: Injector) {  
    const JokeElement = createCustomElement(CustomJokeComponent, { injector });  
    customElements.define('custom-joke', JokeElement);  
    this.showAsElement(20);  
  } 

  showAsElement(id: number) {  
    const jokeEl: WithProperties<CustomJokeComponent> = document.createElement('custom-joke') as any;  
    jokeEl.id = id;  
    document.body.appendChild(jokeEl as any);  
  }  
}
Enter fullscreen mode Exit fullscreen mode

The code in the constructor creates the custom component and attaches it to the DOM with our showAsElement method.

createCustomElement is from our @angular/element code.

The showAsElement method loads our custom-joke Web Component that we defined earlier.

custom-joke.component.ts :

import { Component, OnInit, ViewEncapsulation, Input } from '@angular/core';  
import { JokeService } from '../joke.service';

@Component({  
  selector: 'custom-joke',  
  template: `<p>{{joke?.value?.joke}}</p>`,  
  styles: [`p { font-size: 20px }`],  
  encapsulation: ViewEncapsulation.Native  
})  
export class CustomJokeComponent implements OnInit {  
  @Input() id: number = 1;  
  joke: any = {}; 

  constructor(private jokeService: JokeService) { } 

  async ngOnInit() {  
    this.joke = await this.jokeService.getJokeById(this.id)  
  }}
Enter fullscreen mode Exit fullscreen mode

We put everything in one file so they can all be included in our custom-joke Web Component.

The @Input will be converted to an attribute that we can pass a number into and get the joke by its ID.

We leave custom-joke.component.html and app.component.html blank.

Conclusion

We use the @angular/element package to create a Web Component that we can use.

The difference is that we include the template and styles inline.

Also, we have to register the component and attach it to the DOM.

. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .