Using Angular Elements — Why and How? — Part 1
Learn How To Build Truly Reusable Components with Angular Elements
Angular, Google’s TypeScript based web application framework is currently at version 7 (7.2.4), with version 8 coming out in the next month or so. But I want to talk about another feature of Angular that was added to it in version 6. That feature is Angular Elements.
Angular Elements is a new package in Angular that helps us publish Angular components as custom elements. It does this by taking the Angular component and compiling it into a web component
In this post, I will show you how to use this package in your Angular application. But first, let's take a look at why you should use Angular Elements.
Tip: Building with components? Use Bit to organize share and reuse components between apps with 0 overhead. It’s open source.
Why should we use Angular Elements?
This is a question that every developer should ask themselves, and not just about Angular Elements, but about every other dependency, framework, and third-party library that they want to add to their project. After all, there are thousands of libraries out there and if start adding them to our app just because they are available to us, then your app’s size will be humongous for no reason at all.
Coming back to Angular Elements, here are some of the reason why I think it will make a great addition to your Angular project.
#1. Reusability to a Whole New Level
With Angular Elements, we can make our components truly reusable. Meaning, you can use Angular Components in other frameworks and libraries, such as React, Vue, and Ember!
#2. Use Angular in the Server-Side
With Angular Elements, you can add Angular, which is famously known as a front-end framework, to your project’s backend! Is that cool or what!?
#3. Publish Parts of the Application
Angular Elements also allows you to develop and publish parts of the application independently!
These are just some of the things that first drew me towards Angular Elements. I will show you some more reasons why Angular Elements is great in the following sections, where we see how to use it.
Getting Started
Let’s start by creating a new Angular Project in our system. For this, make sure that you have the latest version of Angular CLI installed. Run the following command in your terminal:
$ npm install -g @angular/cli
Once this install is complete, we can use it to create a new initial application in our system like this:
$ ng new angular-app
You will then asked to select the additional features that you may want to add to your Angular project, things like routing and the type of stylesheet format. Select the default answer by hitting the Enter/Return
key.
The CLI installs the necessary Angular packages and dependencies in a folder called angular-app
. You can serve this application on to the browser by running the following commands:
$ cd angular-app
$ ng serve --open
// --open flag will open the browser for you
As you can see, the app contains already contains a few things that we can display on the screen. But we don’t need them. So go back to your code editor (I ❤️ VS Code) and delete everything inside src/app/app.component.html
file and write a single <h1>
element.
<h1>Welcome To Angular Elements!</h1>
If you take a look at the file structure, you will see that each component in angular consists of a multitude of files. For the App
component alone, there is a .ts
, .html
, .css
, and a spec.ts
file that contains the tests, and a module.ts
file. There will also be an optional routing.module.ts
file if you had chosen to add the routing feature to the application while creating it using the CLI.
Instead of writing all these files for our components, Angular CLI gives us a shorthand method that will generate these files for us. Let’s use this method to create a HelloWorld
component in our system.
$ ng g c HelloWorld
This single command will create a folder named hello-world
inside the app
folder. If you open this file, you will find these 4 files inside it:
- hello-world.component.css
- hello-world.component.html
- hello-world.component.spec.ts
- hello-world.component.ts
We will only be using the .html
and .ts
file. There are additional flags that we can pass to the ng g c
command. But in order to keep things simple and to point, I won’t use them. Check Angular Docs to know more about how to use this command.
Inside the hello-world.component.ts
file, you will see that the selector
is set to app-hello-world
. So, if you add this to the app.component.html
like this:
<h1>Welcome To Angular Elements</h1>
<app-hello-world></app-hello-world>
Then the contents of hello-world.component.html
will be displayed in the browser.
Our initial setup is now ready. Let’s get started with how to use Angular Elements.
Convert A Component Into Custom Element
Right now, we have a simple HelloWorld
component that simply prints some text on the screen. Let’s take a look at how we can insert this component into the app in a more dynamic fashion. To do this, let’s replace the app-hello-world
with a div
and a button
. Let’s also add the click
listener to the button
, so that when someone clicks the button, the HelloWorld
component can be displayed. This will be handled by a showMessage()
function. So go to the app.component.html
page and make these changes:
<h1>Welcome To Angular Elements</h1>
<div id="hello"></div>
<button (click)="showMessage()">Display?</button>
We then need to define the showMessage()
function in the app.component.ts
file. Here we will use regular DOM manipulation to get the div
by selecting it using its id
of hello
. Then, we will apply the innerHTML
function to insert the app-hello-world
component like this:
export class AppComponent {
showMessage() {
const hello = document.getElementById('hello');
hello.innerHTML = '<app-hello-world></app-hello-world>';
}
}
You can go to your browser and click on the button, but I will tell you right now that nothing is going to happen. But then again, if you go the browser’s dev tools and look there, you will see that the HelloWorld
component is inserted.
The reason why we can’t see the component on the screen is that Angular cannot recognize the app-hello-world
tag since it has been inserted into the template dynamically. Angular needs all the elements to be present in the template, or you need to use the dynamic component factory to instantiate the component at runtime through your code.
This is where Angular Elements comes into play. By using Angular Element, all we need to do is insert the tag dynamically, and Angular itself will instantiate the component for us. Let’s finally get to see how to use this component in our app. First, use the ng add
command to install Angular Elements into our app:
$ ng add @angular/elements
Once you have this installed, go to app.module.ts
and make the following changes to the @NgModule
:
import {NgModule, Injector} from '@angular/core';
import {createCustomeElement} from '@angular/elements';
@NgModule({
declarations: [AppComponent,HelloWorldComponent],
imports: [BrowserModule,AppRoutingModule],
entryComponents: [HelloWorldComponent],
providers: [],
bootstrap: [AppComponent]
})
Then in the AppModule
class, write a constructor
function create a new instance of the createCustomElement
and pass it the HelloWorldComponent
as shown below:
export class AppModule {
constructor(injector: Injector) {
const custom = createCustomElement(HelloWorldComponent, {injector: injector});
}
}
Along with the component, we also have to pass it the injector
. The injector
is something that we use to resolve the dependencies. With that, we have created an instance of the HelloWorldComponent
as a custom element. We can then use the customElements
API to define the component’s tag as app-hello-world
.
customElements.define('app-hello-world', custom);
Go back to the hello-world.component.ts
file and remove/comment the selector
inside the @Component
section. This way, Angular will not get confused and will know to use the custom element that we just created.
Rerun the ng serve --open
command to make sure that Angular takes in all the changes that we have just made.
There is a good chance that the button still does not displays the HelloWorldComponent
. This may be because Angular “fails to construct the HTMLElement”. To solve this issue, install the webcomponentjs
polyfills:
$ yarn add @webcomponents/webcomponentsjs
Then, go to the polyfills.ts
file and import the following:
import '@webcomponents/webcomponentsjs/custom-elements-es5-adapter.js';
Finally! Our custom element will get displayed on the screen when we click that button.
Compiling as a Standalone Element
Another benefit of creating custom elements is that they can be compiled into standalone script files that can then be added to different frontend frameworks. This truly makes it Angular Elements as a powerful toolkit. Lets take a look at how we convert our custom element into a standalone script file.
Create a new folder named preview
in the Angular project’s root directory. And inside this folder, create a new file named index.html
and write the following code inside it:
<html>
<body>
<app-hello-world></app-hello-world>
</body>
</html>
Then, go to app.module.ts
and remove the AppComponent
from the declarations
field from @NgModule
. Also remove the boostrap
field completely and add a ngDoBootstrap
function inside the AppModule
class to tell Angular to handle the bootstrapping for us.
export class AppModule {
constructor(injector: Injector) {
const custom = createCustomElement(HelloWorldComponent,{injector});
customElements.define('app-hello-world', custom);
}
ngDoBootstrap() {}
}
Since we don’t need the AppComponent
anymore, we delete all the files related to it. So go ahead and remove the app.component.css
, app.component.html
, app.component.spec.ts
and app.component.ts
files. Be careful, and do not delete the app.module.ts
file, and also remove the app.routing.module.ts
file which you get if you have opted to add the routing
to your app.
After all the deleting, this is how your app’s folder structure should look:
We can now set up a custom build process for our HelloWorldComponent
. Angular CLI already has a build process ready for our application, that will follow the angular.json
file and compile the project’s production build.
So open up your console, and run the build
script.
$ npm run build
You will now see a dist
folder created in your project directory, and it contains a whole lot of files inside it. We need to concatenate all of these files into a single script which we can then import into the HTML file from before.
To do all this, lets create a simple file called custombuild.sh
inside the root folder. A .sh
file is a shell
script file and it will contain the command that will combine all the files.
#!/bin/sh
ng build angular-app --prod --output-hashing=none && cat dist/angular-app/runtime.js dist/angular-app/polyfills.js dist/angular-app/scripts.js dist/angular-app/main.js > preview/angularapp.js
That’s one big command! Let’s make sense of it by breaking it down. First, we want the command to run ng build
script from the Angular CLI. We then give it the name of our Angular app, which in this case is angular-app
. Then we tell the CLI to give us the production
build, and also say that we don’t want any output-hashing
.
Then using the cat
command, we are concatenating the runtime.js
, polyfills.js
, scripts.js
, and main.js
files from the dist/angular-app
folder into a angularapp.js
file inside the preview
folder.
If you are a Mac or Linux user then you can easily run this file inside the terminal like this:
$ ./custombuild.sh
But if you are a Windows user like me, then you will face some problems while running the above command in your terminal. This is because the Windows OS does not come with Bash support.
The quickest support to this is to become a Git user. By installing Git, you also get the Git Bash program, and you can run the program inside it like this:
You will then notice a new file named angularapp.js
created inside the preview
folder, with a whole lot of confusing JavaScript code inside it. All this code describes our HelloWorldComponent
, we can now insert it into the index.html
file using the script
tag like this:
<html>
<body>
<app-hello-world></app-hello-world>
<script src="./angularapp.js"></script>
</body>
</html>
Finally step — Running just this component on the browser. For that, we can use the npx
tool to run the live-server
utility to deploy the component on localhost:8080
.
$ npx live-server preview
Communicating with Angular Elements
Right now, our HelloWorldComponent
is nothing but a single line of text that we got when we created the component using the CLI. Let’s make some changes to it so that it resembles an actual component instead.
First, go to the hello-world.component.html
file inside src/app/hello-world
folders and write the following HTML code:
<div>
<h1>{{title}}<h1>
</div>
Then go to the hello-world.component.ts
file and import the Input
decorator from @angular/core
.
import { Component, OnInit, Input } from '@angular/core';
This decorator is used to control the inputs to the Angular app. We have multiple inputs that we have written in the HTML file, but we still need to define them inside the hello-world.component.ts
file like this:
export class HelloWorldComponent implements OnInit {
@input() title;
@input() rname;
@input() occupation;
@input() location;
@input() first;
constructor() {}
ngOnInit() {}
}
I have created these extra inputs for the future purposes, but our component is now ready! Let’s build it just like we did before by running this command inside a bash shell:
$ ./custombuild.sh
Once this is done, serve the component onto the browser using live-server
:
$ npx live-server preview
But the page is empty! Where is the title
? This emptiness is because we have not given any values to our title
input. Let’s take care of this in the index.html
file that is inside the preview
folder.
<html>
<body>
<app-hello-world
title="Batman
></app-hello-world>
<script src="./angularapp.js"></script>
</body>
</html>
Going back to the browser and you will see the title displayed!
Our component can now take in inputs, but what about events? An event such as a user clicking on a button requires an Output
decorator. So let's import it into the hello-world.component.ts
file. Lets also import the EventEmitter
from @angular/core
:
import {Component, OnInit, Input, Output, Event} from '@angular/core';
Then create a new EventEmitter
function called display
inside the HelloWorldComponent
class.
@Output() display = new EventEmitter();
In order to trigger this EventEmitter
, we can create a button
element inside the html
element like this:
<button (click)="showInfo()">Display</button>
If the user clicks on this button, then the showInfo
function will be executed. Let’s go back to the TypeScript file and define this function like this inside the HelloWorldComponent
class:
showInfo() {
this.display.emit(`Name: ${this.rname}
Occupation: ${this.occupation}
Based In: ${this.location}
First Appearance: ${this.first}`);
}
Go ahead and rebuild the component inside the bash
shell using the ./custombuild.sh
command. Then go to the index.html
file and add values for the remaining inputs that we had created earlier:
<app-hello-world
title="Batman"
rname="Bruce Wayne"
occupation="World's Greatest Detective"
location="Gotham"
first="Detective Comics #27"
></app-hello-world>
All that’s left to take care of is displaying this values. Let’s shake things up and display in the form of an alert when the user clicks on that button. First, create a script
section in the index.html
file and define grab the app-hello-world
component using the querySelector
.
Then, we can use the addEventListener
to listen for our custom event display
. When the app hears this event, we can tell it to display its detail
inside an alert
.
<script>
const component = document.querySelector('app-hello-world');
component.addEventListener('display', (event) => {
alert(event.detail);
}
</script>
The final product should be this:
More?
Until now, we have seen a whole lot of things that Angular and Angular Elements can do for us.
But what if I told you that Angular Elements can do even more? 😲
With Angular Elements, we can:
- Put Angular Components inside other JavaScript Libraries/Frameworks like React and Vue.
- Pass data from React and Vue into the Angular Component
- Use Angular 7 component into an AngularJS app. If you are an Angular developer, then you know how different Angular and AngularJS are!
But this post has already become really long. So let’s continue exploring the uses of Angular Elements in the Part 2 of this series:
Thank you for reading this really, really long post. I hope it helped you understand how to use Angular Elements in your Angular applications. Please feel free to comment here or hit me up on Twitter if you have any thoughts to share with me! Cheers 🍺