Building Reusable UI Components with Angular 6 Custom Elements and Web Components

As a full-stack developer, creating modular, reusable UI components is a key part of building maintainable web applications. Frameworks like Angular provide powerful tools for component-based development, but historically those components have been tightly coupled to the framework itself.

Web components offer a new, standards-based way to create truly reusable UI components for the web. And with the introduction of the Angular elements package in Angular 6, it‘s now easier than ever to leverage Angular‘s abstractions and tooling to build web components that can be used with or without an Angular application.

In this in-depth guide, we‘ll explore what web components are, how they work with Angular custom elements, and walk through a complete example of building and using an Angular custom element. By the end, you‘ll have a solid foundation for creating your own reusable UI components. Let‘s get started!

Understanding Web Components

Web components are a collection of web platform APIs that allow developers to create reusable, encapsulated UI components. They consist of four main standards:

  1. Custom elements: Enables defining new types of HTML elements with custom behavior
  2. Shadow DOM: Provides encapsulation of the component‘s markup, styling, and behavior
  3. HTML templates: Defines the component‘s structure declaratively with reusable markup
  4. ES modules: Enables packaging and distributing web components as reusable modules

The key benefit of web components is that they‘re based on web standards, not a particular library or framework. Components created using web component APIs will work across all modern browsers, regardless of how they‘re implemented internally.

Browser support for web components has grown significantly, with native support now in Chrome, Firefox, Safari, and the new Chromium-based Edge. For older browsers, polyfills can be used to provide the necessary APIs.

Here‘s a breakdown of web component support across popular browsers:

Browser Custom Elements Shadow DOM HTML Templates ES Modules
Chrome
Firefox
Safari
Edge
IE 11 Polyfill Polyfill Polyfill Polyfill

Source: https://caniuse.com (May 2021)

Overall, web components are supported in 77% of browsers globally. So while polyfills are still needed for complete cross-browser support, the majority of web users can take advantage of native web components today.

Why Use Angular Elements?

While it‘s possible to create web components using vanilla JavaScript and the web component APIs directly, the process can be verbose and require a lot of boilerplate. This is where Angular elements come in.

The Angular elements package, introduced in Angular 6, provides an abstraction over the low-level web component APIs. It allows developers to create custom elements using the familiar Angular component model, while still leveraging the browser‘s native web component support.

Some key benefits of using Angular elements include:

  • Simplified API: Angular elements expose a simple createCustomElement function to convert an Angular component to a custom element with a single line of code.

  • Framework-agnostic output: The custom elements produced by Angular elements are truly self-contained, and can be used in any HTML page regardless of whether it uses Angular or not.

  • Reuse across projects: By packaging components as custom elements, developers can more easily share them across projects and teams. This promotes a modular frontend architecture.

  • Improved performance: Custom elements built with Angular elements allow lazy-loading just the necessary components, rather than the entire Angular framework. This can improve application performance.

  • Incremental adoption: For organizations with existing Angular applications, elements provide a path for incrementally migrating to a more modular architecture using web components.

Angular elements diagram

At the same time, there are trade-offs to consider with Angular elements. The additional abstraction may result in larger bundle sizes if the entire Angular framework is included. And styling and theming can be more challenging due to shadow DOM encapsulation. Finally, debugging and testing custom elements requires additional tools and knowledge.

Overall though, Angular elements greatly lower the barrier to creating reusable web components. By building on the mature Angular ecosystem, they‘re a compelling option for organizations investing in Angular.

A Complete Example

Now that we‘ve covered the key concepts behind web components and Angular elements, let‘s walk through a complete example. We‘ll create a simple "counter" component and package it as a reusable custom element.

Prerequisites

To follow along, you‘ll need a recent version of Node.js and the Angular CLI installed. You can install the Angular CLI with:

npm install -g @angular/cli

Step 1: Create a new Angular project

First, we‘ll create a new Angular project to house our custom element:

ng new angular-counter-element
cd angular-counter-element

Step 2: Add the Angular elements package

Next, we need to add the Angular elements package to our project. We can use the ng add command to auto-install the package and its dependencies:

ng add @angular/elements

This will add the @angular/elements package to our project, as well as the necessary polyfills for supporting older browsers.

Step 3: Create the counter component

With our project set up, we can create the counter component that will be packaged as a custom element. Let‘s use the Angular CLI to generate a new component:

ng generate component counter

Open the generated counter.component.ts file and update it with the following code:

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

@Component({
  selector: ‘app-counter‘,
  template: `
    <div>
      <h2>Counter: {{ count }}</h2>
      <button (click)="increment()">+</button>
      <button (click)="decrement()">-</button>
    </div>
  `,
  styles: [`
    div {
      border: 1px solid #ccc;
      padding: 10px;
      display: inline-block;
    }
    button {
      margin: 0 5px;
    }
  `]
})
export class CounterComponent {
  @Input() count = 0;
  @Output() countChange = new EventEmitter<number>();

  increment() {
    this.count++;
    this.notify();
  }

  decrement() {
    this.count--;
    this.notify();
  }

  notify() {
    this.countChange.emit(this.count);
  }
}

Here we‘ve defined a simple counter component with increment and decrement buttons. The component has an input property count and an output event countChange. When the count changes, the component emits the new value via the output.

Step 4: Create a custom element from the component

Now we‘re ready to convert our Angular component to a custom element. Open the app.module.ts file and make the following changes:

import { BrowserModule } from ‘@angular/platform-browser‘;
import { NgModule, Injector } from ‘@angular/core‘;
import { createCustomElement } from ‘@angular/elements‘;

import { CounterComponent } from ‘./counter/counter.component‘;

@NgModule({
  declarations: [CounterComponent],
  imports: [BrowserModule],
  providers: [],
  entryComponents: [CounterComponent]
})
export class AppModule {
  constructor(private injector: Injector) {}

  ngDoBootstrap() {
    const counterElement = createCustomElement(CounterComponent, {
      injector: this.injector
    });
    customElements.define(‘my-counter‘, counterElement);
  }
}

Here‘s what‘s happening:

  1. We import the createCustomElement function from @angular/elements, which converts an Angular component to a custom element class.

  2. We add the CounterComponent to the entryComponents array, which tells Angular to compile the component even if it‘s not referenced in a template.

  3. We define an ngDoBootstrap method, which is called when the application starts up. Inside this method, we use createCustomElement to convert our CounterComponent to a custom element.

  4. Finally, we register the custom element with the browser using the native customElements.define method, specifying the element name and class.

Step 5: Build the custom element

With our custom element defined, we can build it for production using the Angular CLI:

ng build --prod --output-hashing none

This will compile our application and output the built files in the dist directory. We‘ve used the –output-hashing none flag to disable content hashing in the generated file names, which makes it easier to reference them.

Step 6: Package the custom element

The Angular CLI build output will include several JavaScript files (main.js, polyfills.js, runtime.js). To make our custom element easier to use, we‘ll package these into a single file.

Create a new file named package.js in the root of the project with the following contents:

const fs = require(‘fs-extra‘);
const concat = require(‘concat‘);

(async function build() {
  const files = [
    ‘./dist/angular-counter-element/runtime.js‘,
    ‘./dist/angular-counter-element/polyfills.js‘,
    ‘./dist/angular-counter-element/main.js‘,
  ];

  await fs.ensureDir(‘elements‘);
  await concat(files, ‘elements/counter.js‘);
})();

This script uses the fs-extra and concat packages to concatenate the build output into a single file named counter.js in an elements directory.

Install the necessary dependencies:

npm install fs-extra concat --save-dev

Then run the script to package the custom element:

node package.js

Step 7: Use the custom element

Finally, we can use our new custom element! Create an index.html file in the elements directory:

<!doctype html>
<html>
  <head>
    <title>Angular Counter Element Demo</title>
  </head>
  <body>
    <my-counter count="5"></my-counter>

    <script src="counter.js"></script>
  </body>
</html>

Here we‘ve included the packaged counter.js script, and used the element in our HTML markup. We‘ve set the initial count to 5 using the count attribute.

Open the index.html file in a browser, and you should see the counter component rendered. Clicking the + and – buttons will increment and decrement the count.

And that‘s it! We‘ve now created a reusable counter component using Angular elements, and packaged it as a standalone custom element that can be used in any HTML page.

Challenges and Considerations

While Angular elements and web components provide an exciting new way to build reusable UI components, there are some challenges and considerations to keep in mind:

  • Browser support: As mentioned previously, native support for web components is not universal. Polyfills are required for older browsers, which may impact performance.

  • Styling and theming: Due to the encapsulation provided by shadow DOM, styling custom elements can be more challenging than traditional Angular components. Global styles won‘t apply to elements inside a shadow root, so you‘ll need to use special selectors or provide styles directly to the component.

  • Bundle size: If you‘re not careful, the bundle size of your custom elements can quickly grow out of control, especially if you‘re including the entire Angular framework. Be sure to use build optimization techniques like tree shaking and lazy loading.

  • Debugging and testing: Debugging and testing custom elements can be trickier than regular Angular components, due to the custom element lifecycle and shadow DOM. You may need to use browser devtools or special testing libraries that support web components.

Despite these challenges, Angular elements and web components are a powerful addition to the frontend developer‘s toolkit. By providing a standards-based way to create reusable UI components, they promote more modular, maintainable application architectures.

Conclusion and Resources

In this article, we‘ve taken a deep dive into Angular elements and web components. We‘ve covered what web components are, how they relate to Angular elements, and walked through a complete example of creating a custom element using Angular.

As a full-stack developer, adding Angular elements to your skillset can open up new possibilities for creating reusable, portable UI components. By leveraging the power of the Angular framework and the interoperability of web standards, you can build more modular, maintainable applications.

If you‘re interested in learning more about Angular elements and web components, here are some additional resources:

Happy coding!

Similar Posts