Skip to content

mjcctech/origami

 
 

Repository files navigation

NPM Package Commitizen friendly

Build Status

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">

  <!-- This div is needed when targeting ES5. 
  It will add the adapter to browsers that support customElements, which 
  require ES6 classes -->
  <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 // Required for Angular 4 to use native <template>s
  });
});

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, // Origami requires the Angular Forms module
    PolymerModule.forRoot() // Do not call .forRoot() when importing in child modules
  ],
  declarations: [
    AppComponent
  ],
  schemas: [CUSTOM_ELEMENTS_SCHEMA], // Add to every @NgModule() that uses custom elements
  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.

About

Angular + Polymer

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages

  • TypeScript 80.9%
  • JavaScript 19.1%