Mix and Match: Angular + Custom Elements (Polymer)

In my previous post — Polymer 3.0 Preview — Building a mini card game, We use Polymer 3.0 Preview to create custom elements for our mini card game.

Since Angular plays well with custom elements (as mention in https://custom-elements-everywhere.com/, 100% tests passed), let’s try to mix them together!

I would also like to take this opportunity to introduce awesome Angular to my Polymer friends too. :)

Please note that:-

Custom elements ≠ Polymer

Custom elements is part of the web component spec, it is a capability for creating your own custom HTML. It is and will be part of the browser natively. Polymer is one of the tools that simplify the building of custom elements easier. There are many other ways to write custom elements. More explanation here: https://developer.mozilla.org/en-US/docs/Web/Web_Components/Custom_Elements.

Here are the links for:

The Component Structure

Let’s look at our component structure:

We will create a new app component in Angular, while reusing the other 4 Custom Elements (my-top-bar, my-top-message, my-cards, my-pop-up-modal) we created with Polymer 3.0 Preview in previous post.

And here again, the overview of our components.

Setting up Project

Setting up project in Angular is as simple as ABC.

A. Install CLI globally

// npm
npm install @angular/cli -g
// or yarn
yarn global add @angular/cli

B. Creating a new project with CLI

ng new <your-project-name>

C. Start the project, see it live at http://localhost:4200.

// npm
npm start
// or yarn
yarn start

Yes, it’s that simple. Angular CLI does all the miscellaneous tasks for you (of course, it’s always good to find out and learn what exactly it did, then you’ll appreciate more). It’s similar to Polymer CLI. Love all the CLIs! ❤

Including the Custom Elements

Including the custom elements is as simple as including a Javascript file.

  1. Create a scripts folder
  2. Include our custom elements file in the scripts folder.

Where is this file came from? It is from our previous post, it is the JS file generated when you run yarn run build command ( in dist folder, we rename it from app.<random-hash>.js to my-custom-elements.js).

*in actual production environment, one should deploy that as a npm module and npm install it accordingly.

3. Same as previous post, you need polyfills as not all browser fully support custom elements yet. We will be using webcomponents-loader.js. Install this package:

// with npm
npm install @webcomponents/webcomponentsjs --save
// or yarn
yarn add @webcomponents/webcomponentsjs

4. Now, we have these two files in our project. We need to include these two files as part of the build. There are a few ways to do this. In Angular, the quickest way would be to include these file paths in .angular-cli.json file, the assets property like this:

"assets": [
  "...",
  { 
"glob": "webcomponents-loader.js",
"input": "../node_modules/@webcomponents/webcomponentsjs",
"output": "./scripts/"
},
{ 
"glob": "my-custom-elements.js",
"input": "./scripts", "output": "./scripts/"
}
]

With the above lines, Angular will picks these files and output to the scripts folder after build.

5. Load them in our index.html. Add these two lines:

<!-- index.html -->
<script src="scripts/webcomponents-loader.js"></script>
<script src="scripts/my-custom-elements.js"></script>

Cool, we can use these custom elements now. Let’s move to our app component.

* In production however, one should create a file(e.g. vendor.ts) to import all custom elements instead of include it in script tag.

App component template

Let’s take a look at our app.component.html. Here is the code:

compare to Polymer template in previous post:

Polymer’s app template

No big differences right?

All 4 HTML tags above are the custom elements we created using Polymer earlier. Let’s go thought some important notes here (especially if you are from Polymer).

  1. In Angular, we call custom element a component, therefore, you will see I use the term “custom element” and “component” interchangeably.
  2. In Polymer, custom event in template must start with on-*. This is not the case in Angular. For example, in Polymer we handle the reset-clicked as on-reset-clicked. In Angular, we can directly bind that with reset-clicked. We use bracket for Angular event binding, e.g. (reset-clicked).
  3. In Polymer event handler MUST be function name only, without bracket (e.g. resetGame). In Angular, it’s the opposite. It has to be resetGame().
  4. For property binding, Angular user []. For example, we bind the top message component’s time(we call it ‘input’ in Angular) to currentTime variable by using [time]="currentTime".
  5. The conditional statement looks simpler in Angular. Look at the pop up modal, Angular use *ngIf. You might feel weird with the * here. Allow me explain a little bit further here:

In Polymer, you will need to write this long code for conditional statement:

<template is="dom-if" if="...">
<my-pop-up-modal ...></my-pop-up-modal>
</template>

You can definitely write something similar in Angular:

<ng-template [ngIf]="...">   
<my-pop-up-modal ...></my-pop-up-modal>
</ng-template>

Rest assure that the above syntax is a valid Angular conditional syntax, and will work exactly as expected.

However, even better, Angular gives you a shortcut *ngIf, it is a syntactic sugar, so you can achieve the same result with less complicated syntax.

<my-pop-up-modal *ngIf="..."></my-pop-up-modal>

Isn’t that cool? Angular provide a pretty detail explanation on the * syntax here. Check it out.

App component TS

Next, look at our component code:

The code logic is exactly same as the app component in my previous post. Just copy pasted it over. With a few things to note here :

Component class

In Polymer, a custom element must be a class. We create a new element by extending the PolymerElement class.

In Angular, a component is a class as well. However, you don’t need to extend from any base class, simply use the @Component class decorator to mark it as Angular component.

* Decorator is currently in stage 2 TC39 https://tc39.github.io/proposal-decorators/

Template, styling and name

When defining the template in Polymer, you need to override the template getter, css live in template as well. The custom element name must be in kebab case. One assign element’s name by override the is getter or do it during customElements.define.

In Angular, template can be either inline or external file. You can do it by configure the metadata object in @Component class decorator, template or templateUrl property. In our case, we are using external template.

Component name in Angular can be either kebab case my-component or camel case myComponent. We define the name in metadata selector property.

Styling in Angular component can be done both inline or by external files as well. You can configure that with styleUrls and styles metadata property. Since we are using Angular CLI, SCSS and LESS are supported out of the box.

Angular CLI also provide an easy way to generate new component by running ng generate component <your-component-name>. Pretty handy.

Lifecycle hooks

Polymer has lifecycle hooks, so does Angular. The closest lifecycle to Polymer ready would be Angular ngOnInit. Read the full list of Angular lifecycle events is here.

Nice. We are almost there. Left the ONE LAST STEP.

Allowing custom elements

Refreshing your page now, you should see a blank page. Look at the console, you will get a long list of errors:

Why? It’s because:

  1. my-top-bar is not a Angular component, no @Component class created for that.
  2. However, my-top-bar is a custom element that we have. Angular doesn’t know about that. Therefore, we need to tell Angular that “Hey, it’s ok, I know what I am doing, I have some custom elements that you don’t know, stop throw me error please”)

Open your app.module.ts file, add in these lines:

More explanation about schema here: https://angular.io/api/core/NgModule#schemas

Play!

Great. Refresh your browser again, everything should work like a charm.

That’s it. Happy coding!

Here are the links for: