Origami
Origami is the art of folding paper with sharp angles to form beautiful creations.
Angular + Polymer
Intro
Origami bridges the gap between the Angular platform and Polymer-built web components. This opens up a huge ecosystem of high quality custom elements that can be used in Angular!
"The Gap"
Angular and custom elements are BFFs. There are only a few areas specific to Polymer that Origami can help out with.
- Angular Template/Reactive Form Support (
[(ngModel)]
) - Native
<template>
elements - Seamless production-ready build process
Setup
1. Install Dependencies
npm i --save @codebakery/origami
npm i --save-dev babel-loader babel-core babel-preset-es2015 polymer-webpack-loader script-loader
Origami needs to patch the Angular CLI to insert the webpack loaders that we installed. Modify your package.json
and add a postinstall script to create the patch.
package.json
{
"scripts": {
"postinstall": "node ./node_modules/@codebakery/origami/patch-cli.js"
}
}
npm run postinstall
Now anytime you install or update the Angular CLI, Origami will check and apply the patch if needed.
2. Use Bower to add elements
Bower is a flat dependency package manager for web modules. Polymer 2 and many elements are hosted with it.
npm i -g bower
bower init
3. Load polyfills
We're going to use a dynamic loader to only add polyfills if the browser needs them. In order to do this, Angular needs to include all the polyfill scripts at runtime as part of its assets.
Since we'll be referencing these assets in our index.html
, they must be part of the app's root directory. A typical Angular CLI-generated project will have a src/
directory that is the app root.
We can move where bower dependencies are installed with a .bowerrc
file in the project directory.
.bowerrc
{
"directory": "src/bower_components/"
}
Now all bower dependencies are available for .angular-cli.json
and our index.html
. This example is using src/bower_components/
as the directory to install to, but this may be any folder name that exists in the app root directory.
Like node_modules/
you should add src/bower_components/
to your .gitignore
file to prevent checking them in.
Now that bower is installing where the project files can see it, install the webcomponents polyfill.
bower install --save webcomponentsjs
Modify .angular-cli.json
and add the following to your app's assets.
.angular-cli.json
{
"apps": [
{
"root": "src",
"assets": [
"assets",
"favicon.ico",
"manifest.json",
"bower_components/webcomponentsjs/custom*.js",
"bower_components/webcomponentsjs/web*.js"
],
/* remaining app config */
}
],
/* remaining CLI config */
}
Next, modify the index.html
shell to include the polyfills.
index.html
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Origami</title>
<base href="/">
<meta name="viewport" content="width=device-width, initial-scale=1">
<div id="ce-es5-shim">
<script type="text/javascript">
if (!window.customElements) {
var ceShimContainer = document.querySelector('#ce-es5-shim');
ceShimContainer.parentElement.removeChild(ceShimContainer);
}
</script>
<script type="text/javascript" src="bower_components/webcomponentsjs/custom-elements-es5-adapter.js"></script>
</div>
<script src="bower_components/webcomponentsjs/webcomponents-loader.js"></script>
</head>
<body>
<app-root>Loading...</app-root>
</body>
</html>
Custom elements must be defined as ES6 classes. The custom-elements-es5-adapter.js
script will allow our transpiled elements to work in ES6-ready browsers. webcomponents-loader.js
will check the browser's abilities and load the correct polyfill from the bower_components/webcomponentsjs/
folder.
The last piece is to wait to bootstrap Angular until the polyfills are loaded. Modify your main.ts
and wait for the polyfills.
main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { webcomponentsReady } from '@codebakery/origami';
import { AppModule } from './app/app.module';
webcomponentsReady().then(() => {
platformBrowserDynamic().bootstrapModule(AppModule);
});
Angular 4 templates
Angular 4 consumes native <template>
tags, which are commonly used in web components. Add the following configuration to the app's bootstrap to prevent this.
main.ts
webcomponentsReady().then(() => {
platformBrowserDynamic().bootstrapModule(AppModule, {
enableLegacyTemplate: false
});
});
enableLegacyTemplate: false
will prevent Angular 4 from turning native <template>
elements into <ng-template>
s. Bootstrap options must also be specified in your tsconfig.json
for Ahead-of-Time compilation.
tsconfig.app.json
{
"compilerOptions": {
...
},
"angularCompilerOptions": {
"enableLegacyTemplate": false
}
}
Angular 5+ defaults this value to false
. You do not need to include it in your bootstrap function or tsconfig.json
.
4. Import Origami
Import Origami into your topmost root NgModule
. In any modules where you use custom elements, add CUSTOM_ELEMENTS_SCHEMA
to the module. This prevents the Angular compiler from emitting errors on unknown element tags.
app.module.ts
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { BrowserModule } from '@angular/platform-browser';
import { PolymerModule } from '@codebakery/origami';
import { AppComponent } from './app.component';
@NgModule({
imports: [
BrowserModule,
FormsModule,
PolymerModule.forRoot()
],
declarations: [
AppComponent
],
schemas: [CUSTOM_ELEMENTS_SCHEMA],
bootstrap: [AppComponent]
})
export class AppModule { }
5. Import and use custom elements
Install elements! Persist them to bower.json
with the --save
flag.
bower install --save PolymerElements/paper-checkbox
bower install --save PolymerElements/paper-input
Next, import the element in the Angular component that you want to use it in. Add the [ironControl]
directive to elements that use Angular form directives.
app.component.ts
import { Component } from '@angular/core';
import 'paper-checkbox/paper-checkbox.html';
import 'paper-input/paper-input.html';
@Component({
selector: 'app-root',
template: `
<div>
<label>Hello from Angular</label>
<input [(ngModel)]="value">
</div>
<paper-input label="Hello from Polymer" ironControl [(ngModel)]="value"></paper-input>
<div>
<label>Non-form two-way bindings!</label>
<input type="checkbox" [(value)]="checked">
</div>
<paper-checkbox [checked]="checked" (checked-changed)="checked = $event.detail.value"></paper-checkbox>
`
})
export class AppComponent {
value: string;
checked: boolean;
}
Support
- Angular 4.2.0 +
- Polymer 2.0 +
Origami does not support Polymer 1. Check out angular-polymer if you need Polymer 1 support.
Browsers
- Chrome
- Safari 9+
- Firefox
- Edge
- Internet Explorer 11
Polymer and Angular support different browsers. Using Polymer means that you will lose support for IE 9 and 10 as well as Safari/iOS 7 and 8.