Create and use Custom Elements with Angular
Older Article
This article was published 8 years ago. Some information may be outdated or no longer applicable.
We’ll look at the custom elements feature in Angular6: how to create one and how to reuse it in a simple application.
Web Components
Before we get into the details, let’s spend a moment on web components. Web components let us create custom elements that are encapsulated, carry their own functionality, and can be reused in any web app.
Web components have three parts:
- Custom elements: a JavaScript API that lets us define custom elements and their behaviour
- Shadow DOM: Enables encapsulation. The styles and scripts added to our element stay “private” and won’t interfere with other parts of our document/application
- HTML templates: Using templates we can create reusable code. Markup placed between
templateelements won’t be visible but can be referenced by JavaScript and appended to the DOM.
One interesting fact about the Shadow DOM: you’ve probably used it before without realising. The HTML
<video>element uses the Shadow DOM. It places controls (if the attribute is specified) which adds a whole bunch of items to the shadow-root.
Angular Elements
With Angular6, we can create custom elements via a package called Angular Elements. It’s installed separately: npm i @angular/elements (or via ng add @angular/elements).
This package gives us access to a createCustomElement() function that transforms any Angular component into a custom element. Sounds too easy? It genuinely is. We can now turn any component into a custom element with minimal effort.
Think of these custom elements as Angular components stripped of Angular-specific knowledge. When we include them in an application, we don’t need to bring any Angular-specific items along. They work as standalone pieces.
Creating the application
Let’s walk through creating a custom element. As with any Angular project, we’ll start by generating a new project via the CLI: ng new custom-element.
Once dependencies are installed, cd into the folder and add the @angular/elements dependency: ng add @angular/elements.
Next, create a new component: ng g c greeter.
This component is going to be simple (apologies for the lack of creativity, but I want to show the custom element creation without spending ages explaining the component itself). It takes a name attribute and produces a greeting.
Here’s the component template:
<span>Hi, it's nice to meet you {{ name }}! 👋</span>
And the corresponding TypeScript:
import { Component, OnInit, Input } from '@angular/core';
@Component({
selector: 'app-greeter',
templateUrl: './greeter.component.html',
styleUrls: ['./greeter.component.css'],
})
export class GreeterComponent implements OnInit {
@Input()
public name: string;
constructor() {}
ngOnInit() {}
}
Test the application
Go ahead and test by adding the following to app.component.html:
<app-greeter name="John"></app-greeter>
Run ng serve and open the browser to see it working. The result should look like the screenshot below:

Creating the custom element
Time to transform our component into a custom element. A few things need doing.
First, we make some changes to app.module.ts:
import { BrowserModule } from '@angular/platform-browser';
import { NgModule, Injector } from '@angular/core';
import { AppComponent } from './app.component';
import { GreeterComponent } from './greeter/greeter.component';
import { createCustomElement } from '@angular/elements';
@NgModule({
declarations: [
AppComponent,
GreeterComponent
],
imports: [
BrowserModule
],
providers: [],
entryComponents: [ GreeterComponent ]
})
export class AppModule {
constructor(private injector: Injector) { }
ngDoBootstrap() {
const myCustomElement = createCustomElement(GreeterComponent, { injector: this.injector });
customElements.define('app-greeter', myCustomElement);
}
}
We’ve imported createCustomElement from @angular/elements and Injector from @angular/core. Notice we’ve removed the bootstrap property entirely from the module definition and added entryComponents instead.
We’ve also added code to the AppModule class. The key piece is ngDoBootstrap(), which creates the custom element when the Angular application bootstraps.
createCustomElement() takes two parameters: the Angular component we want to turn into an element, and a configuration object (in our case, the current Injector instance).
The second part of ngDoBootstrap() defines the custom element. This isn’t Angular-specific. customElements is a property of the Window interface, referencing the CustomElementRegistry object. Its define() method lets us register a new custom element.
Now we build the Angular project to create the actual element: ng build --prod --output-hashing none.
Wondering about
--output-hashing none? It strips hashes from the generated JavaScript filenames. We’re doing this because we’ll concatenate all these files later, and we don’t need hash values in the names.
The build produces a dist folder with JavaScript files and an index.html. The custom element is ready, but at the time of writing, Angular doesn’t offer a build option for creating pure custom elements on their own. We can set up a separate build process for that.
Let’s add gulp: npm i gulp gulp-concat and create a gulpfile.js:
const gulp = require('gulp');
const concat = require('gulp-concat');
gulp.task('concat', function () {
return gulp
.src([
'./dist/custom-element/runtime.js',
'./dist/custom-element/polyfills.js',
'./dist/custom-element/scripts.js',
'./dist/custom-element/main.js',
])
.pipe(concat('app-greeter.js', { newLine: ';' }))
.pipe(gulp.dest('./dist/'));
});
gulp.task('default', ['concat']);
This concatenates all the JavaScript files into one. Run gulp via node_modules/gulp/bin/gulp.js. This generates an app-greeter.js file in the dist folder.
Now we can try the custom element out.
Move app-greeter.js somewhere else and create an index.html with (at minimum) the following:
<script src="app-greeter.js"></script>
<app-greeter name="Tamas"></app-greeter>
Serve this HTML file (via http-server, Python’s Simple HTTP server, anything really). The result should look the same as running the Angular app directly.
The resulting JavaScript file (
app-greeter.js) weighs 244k, which is considerable. Future Angular releases are expected to include CLI support for building custom elements, and the new rendering engine (Ivy) should help shrink the size.
Conclusion
Building custom elements with Angular is fairly easy, even though some manual steps are still required. At the moment, custom elements are mainly intended for use inside Angular6 applications. Going forward (perhaps Angular7?), support will grow, both within Angular apps and outside them.