Which workflow to integrate Polymer with Angular?


Polymer. Its promises to allow the development of UI component libraries without being locked into a technical stack, relying on the (future) capabilities of the browser. Simply write its components and reuse them everywhere with the guarantee of not generating side effects. It is desirable, no ? If you work with several web technologies and want to develop a catalog of generic UI components, Polymer might seem like a good solution.
We will see together how to integrate a component written with Polymer 2.0 in an Angular application.

Preamble: Polymer and Web Components

If there were to be just one thing to remember:
Polymer != Web Components
Web Components, which first appeared in 2011, are a set of 4 technologies for creating and using elements whose style and code are encapsulated from the rest of the application. These 4 technologies are based on W3C specifications, some of which are already in Living Standard status.
In 2013, engineers at Google wanted to make Web Components accessible by creating an overlay to these technologies. Their goal is to be able to start writing and using Web Components without waiting for the specifications to be complete and implemented in enough browsers. This overlay, sweetly named Polymer, was destined to become lighter – or even disappear – in proportion to browser coverage.
These 4 technologies are:

Custom Elements

These are JS APIs for defining your HTML elements and their associated behavior. After the call to CustomElementRegistry.define(), you can then use the corresponding tag in your HTML.
They also define the Life cycle callbacks of the element that Polymer picks up.

The Shadow DOM

This is what will make it possible to encapsulate the HTML structure and style of the element, without the outside world being able to interfere in an uncontrolled way.
Polymer manages the Shadow DOM for us, while allowing us to manipulate it

HTML Templates

This techno defines two elements. <template>, which allows you to write unrendered HTML that will be copied to elements that use them. the <slot> lets us write a space in our HTML structure where the child elements of our element will be inserted.

HTML Imports

If you wrote your Web Component in its dedicated .html file, one way to use it in your application is to import it via an HTML import:

<link rel="import" href="myfile.html">

This techno is controversial and will ultimately not be adopted by the standard. According to Firefox, the present and future tools (ES6 Module) are sufficient and offer more control than HTML Import.

Polymer additions

Beyond making these 4 technologies work on all browsers, you will find in the Polymer toolbox something to facilitate the development of Web Components:
– Helper to declare the properties of our component (default value, read-only, calculated, observer function, automatic deserialization)
– Event management (automatic subscription & unsubscription, gestural events for mobile users)
– Data management, with 2-way data-binding
– Custom elements equivalent to ngIf and ngFor of Angular
– and various utilities such as a debouncer to avoid calling a callback at too tight an interval
In addition to these basic functionalities, Polymer provides solutions for developing complete applications (route management, internationalization, offline management), not very useful since it is already managed by Angular.

and Angular?

Angular started after Polymer, in September 2014. But unlike Polymer, Angular components are not Web Components. They use techniques specific to these, such as an emulation of the shadow dom (which can also be activated natively) for the encapsulation of the style, but you cannot use an Angular component in an environment other than Angular.

Angular & Polymer integration

To illustrate this integration, we will see two ways: one simple and fast, convenient for testing, and the other more suitable for a production environment.
Either way, you'll need bower.

The easy way

In this case, the customs elements will be developed locally, related to the Angular project. The elements are imported into the index.html.

  1. At the root of your project, initialize bower with
$ bower init

Prompt requests are not interesting because we will not publish via bower. Our use will be limited to managing Polymer dependencies.

  1. Still at the root, create a file .bowerrc with this content:
{
  "directory": "src/assets/bower_components/"
}

where assets corresponds to your Angular assets directory.

  1. In the .gitignore, add the same path as the directory du .bowerrc.

For the example, we will use elements developed by the Polymer team.

  1. Install the items paper-slider et paper-card
$ bower install --save PolymerElements/paper-slider PolymerElements/paper-card
  1. Add your dependencies in the index.html.
<head>
  <link rel="import" href="assets/bower_components/paper-card/paper-card.html">
  <link rel="import" href="assets/bower_components/paper-slider/paper-slider.html">
</head>
<body>
  <app-root></app-root>
</body>
  1. To be able to use your new elements, you must authorize tags unknown to Angular. In each module declaring components using custom elements, you must apply the value CUSTOM_ELEMENTS_SCHEMA to the property schemas :
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from "@angular/core";
@NgModule({
  declarations: [AppComponent],
  bootstrap: [AppComponent],
  schemas: [CUSTOM_ELEMENTS_SCHEMA],
})
export class AppModule {}

And There you go ! If you want to develop your WebComponents locally, put them in a folderassets. Don't use polymer-cli which will generate everything you need to publish, and will conflict with stuff in bower_components.
In your HTML defining your WebComponent, you will be able to import the Polymer retrieved after installing elements from bower:

<link rel="import" href="../bower_components/polymer/polymer.html">

The right way to produce

Rather than loading all of your components into your application's entry point, you'll import them into your Angular components on the TypeScript side. These will be embedded in the chunk corresponding to your component's module, which is interesting when you lazy-load your modules. Thus, you greatly reduce the initial charge.
This is made possible thanks to the project Polymer Webpack Loader which will transform your HTML element definitions into a JS bundle.
PolymerWebackLoader operation
That's good, Webpack is already present and configured via Angular CLI. You can patch the CLI generated config using Origami (Polymer + Angular) and see a usage example with this starter kit.
Be careful though, if your elements host images, you will have to change the way they are used. Either by moving the images into Angular's asset folder so that the importPath of Polymer corresponds, either by referring to it via a variable:

const img = require('./checked.png');

so that the image is included in the bundle.

Integration with Angular forms

To integrate a Polymer element into an Angular form, you have to make sure that they speak the same language!
Using the directive ngDefaultControl on your component, you make sure that Angular will take events into account input emitted by your component. That is, when you want the value of the FormControl Angular is refreshed by the Polymer element, you will need to emit an event input. For example for a text field, input can be emitted after each keystroke.

class DemoElem extends Polymer.Element {
  static get is() { return 'demo-elem'; }
  static get properties() {
    return {
      value: {
        type: String
      }
    };
  }
  notifyChange() {
    this.dispatchEvent(new CustomEvent('input'));
  }
}

Then Angular will retrieve the current value via the property value of the component.
If you are using a fetched WebComponent that does not emit input, you can pass through a directive that will implement ControlValueAccessor. The Angular team has developed a good slew of them (here) that we can use as a model if we do not find what we are looking for. This is where you can listen to the component event and interpret the result before passing it to the Angular form, and vice versa when the Angular form wants to set a value for that control.
You can find an example of implementation in the repo of demo.
Normally, the validation of a control is always done on the Angular side, via the Validators. Angular can tell a WebComponent that it is invalid via data-binding. It is managed by the WebComponents developed by the Polymer team which provides a property invalid et error-message. In some complex cases, you may want to manage validity in the component (to have better control over formatting, for example). To make Angular aware of the invalidity of this component, rather than duplicating the validation code, you can catch an event generated by the component to indicate that its validity state has changed.

Conclusion: The Future of Web Components with Angular

We have seen that the integration of Polymer 2.0 in Angular could complicate the workflow (especially if it is a form control). With Polymer 3.0, we will move from Bower to NPM and from HTML Import to ES6 modules, making the build step necessary. As an alternative, Angular is developing Angular Elements, which will allow us to export our Angular components into Web Components that can be used anywhere! In the future, I think there will therefore be less interest in using Polymer in addition to Angular, if both allow as much reusability… It will be a matter of taste!
Github demo project
Thibault Chevrin, JS-Craftsman @JS-Republic