Published on 6 min read

Building a Custom Element Using Angular Elements

Follow me:
Building a custom element with Angular Elements and rendering an Angular component inside a non-Angular project such as React.

Update: I’ve published an up-to-date article about Angular Elements. Check it out to learn about the latest API of Angular Elements. Note that the Labs API shown here (registerAsCustomElements) never shipped.

Keep reading this article in order to get a sense of the concept.

Preface

Two and a half months have passed since Angular Elements project was revealed at the AngularConnect conference and shocked the Angular community.

Along with a lot of excitement and expectation, it’s really important to remember that:

As of today, Angular Elements is just an experimental project of the Angular Labs.

Don’t get me wrong - this project is brilliant, without a doubt. But, you should know that it’s not ready for production purposes, or even for development.

For those who aren’t familiar with the Angular Labs term - it’s an initiative announced at AngularMix for exploring new ideas before they’re released officially. In other words, it’s the place where the really cool things are started.

The logo of Angular Labs
The logo of Angular Labs

This article examines the benefits of Angular Elements, through creating an experimental custom element and rendering it inside a non-Angular project, such as React.

What are Angular Elements?

Imagine that we’ve developed an awesome Angular component. However, in real life, not all the web applications are Angular-based or even single-page. We wish to use our amazing component as part of any web application in a non "hacky" way. Soon, there will be a neat way and it’ll be called - Angular Elements.

Angular Elements are ordinary Angular components, which are packaged as Custom Elements. In fact, Angular takes charge of initializing a custom element and bridging the attributes, events and lifecycle hooks between the component and the custom element.

"Angular Component on the inside, standards on the outside." (Rob Wormald)

This approach lets us develop reusable components in the way that’s familiar to us, and yet, embedding these in every kind of website (vanilla JavaScript, React, Vue, WordPress, etc. - it really doesn’t matter).

Well, are you ready to build a custom element? 😉

Building a Custom Element

Let’s create a component to show the world how much we love the open source community:

@Component({
  selector: 'made-with-love',
  template: `
    <ng-template #noUrl>
      {{ name }}
    </ng-template>

    <span [style.font-size.em]="size">
      Made with <span [style.color]="color">♥</span> by
      <ng-container *ngIf="url && url.length > 0; else noUrl">
        <a [attr.href]="url" target="_blank">{{ name }}</a>
      </ng-container>
    </span>
  `
  styleUrls: ['./made-with-love.component.scss']
})
export class MadeWithLoveComponent implements OnInit {
  @Input()
  public name: string;

  @Input()
  public url: string;

  @Input()
  public color: string = 'red';

  @Input()
  public size: number = 1;

  ngOnInit() {
    if (!this.name || this.name.length === 0) {
      console.error(`Name attribute must be provided!`);
    }
  }
}

Notice that’s an ordinary component, which displays a "Made With Love" message and receives a few parameters: the developer name, his website, a valid color format (RGB, Hex, etc.) and a size for the heart. The name attribute is mandatory, so we log an error in case it’s undefined or empty. We bind these attributes inside the appropriate template and display a non-clickable developer name when the URL isn’t provided.

The styles file is pretty straightforward, thus we won’t dive into it.

The next step is attaching the source code of @angular/element package inside our project, in order to import the registerAsCustomElements function and the other necessary files. In practice, we should avoid that way of importation. However, these files aren’t available at this time on master, so when these are merged into master, we’ll install and import them like a regular Angular package. This package is targeted to be published as part of Angular 6. Update: It’s already available on master and you should install it like a regular package. Read my new article.

Now, we register our component with the AppModule:

import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

import { MadeWithLoveComponent } from './made-with-love/made-with-love.component';

export const customElements = [
  MadeWithLoveComponent
];

@NgModule({
  imports: [
    BrowserModule
  ],
  declarations: [
    ...customElements,
  ],
  entryComponents: [
    ...customElements
  ]
})
export class AppModule {
  ngDoBootstrap() { }
}

In the above example, we create an array of custom elements. Also, we register its items with the declarations and entryComponents arrays. In case you didn’t know, "Entry Components" is an array of components which aren’t embedded in a particular template, but still created somehow imperatively.

As opposed to other AppModule files - we don’t register a bootstrap component with this module, because we don’t have (and need) one like that. This is why ngDoBootstrap is implemented as an empty stub as well.

Great, we’re making progress. So far, we created a component and registered it with a module, but we haven’t done anything specific to Angular Elements yet. Let’s get the hang of it!

As you know, there’s a main.ts file for each Angular application. This is used as the main entry point which bootstraps the application. As mentioned above, we don’t have a bootstrap component. However, our custom element should have a self-bootstrapping capability. So, in order to achieve that, let’s use the registerAsCustomElements function:

import './polyfills';

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { registerAsCustomElements } from './@angular/elements';

import { AppModule, customElements } from './app/app.module';

registerAsCustomElements(customElements, () => {
  return platformBrowserDynamic().bootstrapModule(AppModule);
})
  .then(() => {
    // App is bootstrapped
  }, (error) => {
    // There's a bootstrapping error
  });

As you can see, we pass the customElements to that function. After the bootstrapping process finishes, it takes each component in the provided array and wraps it with the Custom Element Web API. Currently, the browsers don’t support custom elements perfectly and thus we load several polyfills.

Take a look at the final result:

Live Previews

As I promised you, here’s an example for a React project that renders our custom element:

Wrapping Up

Angular Elements will provide a way to share our components everywhere on top of custom elements (web components). We covered this new way with a brief introduction and building an embeddable Angular element.

Here are some references you’d probably like to check out:

You’re welcome to share:

Join My Newsletter

Get new posts directly to your inbox.

Unsubscribe at any timePrivacy Policy

Comments are powered by DisqusDetails

Loading comments activates Disqus, which collects information as a third-party service.

The site owner has no access to or control over the information collected by Disqus.