Creating Custom Elements using Angular

Custom Elements is one of the key features of the Web platform. Custom Elements extend HTML by allowing you to define a tag whose content is created and controlled by JavaScript code. The idea is that you can use something like <app-chat-form></app-chat-form> along with the standard elements like <h1>,<input> etc. This feature currently supported by Chrome, Edge (Chromium-based), Firefox, Opera, and Safari, and available in other browsers through polyfills. You can learn more about custom elements here and here.

Angular Elements allow us to expose our components as reusable elements. We can use the Angular elements in other angular applications or non-angular applications such as HTML, React, PHP, WordPress etc. as well. In general Angular Elements are Angular Components packaged as custom elements. @angular/elements package exports a createCustomElement() API that provides a bridge from Angular's component interface and change detection functionality to the built-in DOM API. It automatically converts your component to equivalent native HTML elements.

Following are the Step by Step process for creating custom elements with Angular. Here I am creating a simple contact form.

1) Create an Angular project

Create an Angular project in CLI using below command
  ng new angular-chat-form --routing=false --skip-tests=true --style=css
I am not using the routing and tests for my project and using css for styling

2) Create a chat component

Lets create an angular component which will turn as embeddable web component later.
ng generate component chat-form
or 
ng g c chat-form
This will generate the typescript, html template and css files for the component and below are the code blocks in each file. You can add your own design and code here.

chat-form.component.html
{{title}}
Name is required
Email is required

chat-form.component.css
* {box-sizing: border-box;}

/* Button used to open the contact form - fixed at the bottom of the page */
.open-button {
  cursor: pointer;
  position: fixed;
  bottom: 0px;
  right: 28px;
  width: 280px;
}

/* The popup form - hidden by default */
.form-popup {
  position: fixed;
  bottom: 0;
  right: 15px;
  z-index: 9;
  width: 400px;
}

chat-form.component.ts
import { Component, Input, OnInit } from '@angular/core';
import { NgForm } from '@angular/forms';

@Component({
  selector: 'app-chat-form',
  templateUrl: './chat-form.component.html',
  styleUrls: ['./chat-form.component.css']
})
export class ChatFormComponent implements OnInit {

  @Input() id: number = -1;
  @Input() title: string = "Sign Up! for News Letter";
  @Input() buttonTitle: string = "Subscribe for News Letter";
  formOpen = false;
  formData = { name: '', email: '' };
  formSubmitted = false;

  constructor() { }

  ngOnInit() {

  }
  openForm() {
    this.formData = { name: '', email: '' };
    this.formSubmitted = false;
    this.formOpen = true;
  }
  onSubmit(form: NgForm) {
    this.formSubmitted = true;
    if (!form.valid)
      return;
  }
  closeForm() {
    this.formOpen = false;
  }
}
Now my component is ready. Here I used bootstrap for the design in my application. 

3) Add the Angular Elements package

We need to get the @angular/elements library, so we run
ng add @angular/elements 
This will bring the library inside our node_modules folder. It also adds the document-register-element.js polyfill required for web browsers that don't support custom elements yet and the @angular/elements package.

Now we need to tell Angular, not to treat our component as common Angular component. We can do in it in the module bootstrapping level. For this, we need to implement ngDoBootstrap method in our AppModule and tell our module to define a custom element by using the createCustomElement function in @angular/elements package. Below is how my app.module.ts looks like after these changes (Removed the AppComponent). 

app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { DoBootstrap, Injector, NgModule } from '@angular/core';

import { ChatFormComponent } from './chat-form/chat-form.component';
import { FormsModule } from '@angular/forms';
import { createCustomElement } from '@angular/elements';

@NgModule({
  declarations: [
    ChatFormComponent,
  ],
  imports: [
    BrowserModule,
    FormsModule
  ],
  providers: [],
  entryComponents: [ChatFormComponent]
})
export class AppModule implements DoBootstrap {
  constructor(private injector: Injector) {
    const customElement = createCustomElement(ChatFormComponent, { injector });
    customElements.define('app-chat-form', customElement);
  }

  ngDoBootstrap() {

  }
} 
Here,
  1. Add ChatFormComponent inside entryComponents array property of NgModule and remove the AppComponent
  2. We need to pass the injector to our component manually to ensure dependency injection works at runtime.
  3. We put our component in entryComponents list for bootstrapping the web component.  
  4. createCustomElement turns our component to web component. We provide the output of this function to customElements.define function.
  5. customElements.define tells how our component can be called from other HTML pages. We can override our Angular component selector here. In this case, I used the same name but we can use any two or more words separated with hyphen (as per Custom Elements API, name should contains at least one hyphen. Check here for more details. 
    customElements.define('chat-form', customElement);
    
  6. Finally we are using ngDoBootstrap() to manually bootstrap the app module.


4) Build the application

Our next step is to build the application. 
ng build
At this point, we can find the following files in dist folder.


You will not get the vendor.js file if you build for prod as below.
ng build --prod --output-hashing=none
in this case, your dist folder will look like below









5) Using the custom element in HTML page

We need to include these files in app where we need to use our custom element as below.


	
	AngChatForm
	
	


	

Angular Elements demo

Note: 
  1. We need to add bootstrap CDN as we used bootstrap npm
  2. The input elements naming pattern is different for custom elements. When we use Camel case (buttonTitle) in our Angular element input variable, then we need to use Kebab case (button-title)

It will work as below

Chrome





IE 11




Exporting the element as Single .js File

Generally, when we run the ng build or ng build -prod, it will create different files runtime, main, polyfills, vendor etc files in dist folder and we need to refer all these files in our target application. For convenience, we can bundle our Angular element as a single .js file using jscat npm. Install the jscat library as below.
npm install jscat
Modify the build command in package.json to build our app for prod mode. Make hashing off, so that we will get the files with standard names like main, polyfills etc.
Add another command called package, package2015 to concatenate all the required script files into 1 script file called ang-chat-button.js  file inside script section. 

Now my package.json scripts section will looks like as below
 "scripts": {
    "ng": "ng",
    "start": "ng serve",
    "build": "ng build --prod --output-hashing=none",
    "test": "ng test",
    "lint": "ng lint",
    "e2e": "ng e2e",
    "package": "jscat ./dist/ang-chat-form/runtime-es5.js ./dist/ang-chat-form/polyfills-es5.js ./dist/ang-chat-form/main-es5.js ./dist/ang-chat-form/vendor-es5.js > ./dist/ang-chat-button.js",
    "package2015": "jscat ./dist/ang-chat-form/runtime-es2015.js ./dist/ang-chat-form/polyfills-es2015.js ./dist/ang-chat-form/main-es2015.js ./dist/ang-chat-form/vendor-es2015.js > ./dist/ang-chat-button-es2015.js"
  }
Now just run the below command
 
npm run build && package
or (for ES6 support)
 
npm run build && package2015
It will build the application and create a file called ang-chat-button.js in "dist" folder.




Now you can use this one file in your target application along with the css files. The above html is now simplified as 
 



	
	AngChatForm
	
	


	

Angular Elements demo

You can find the code created for this post here.
Happy Coding 😀!



Gopikrishna

    Blogger Comment
    Facebook Comment

0 comments:

Post a Comment