You are viewing documentation for Vaadin Framework 8 and related products View documentation for Vaadin Framework 7 ›
Angular 2 with Polymer Elements Tutorial · Vaadin
Vaadin Elements - Angular 2 Integration - Angular 2 with Polymer Elements Tutorial

Angular 2 with Polymer Elements Tutorial

1. Introduction

This tutorial explains how to use Polymer elements in Angular 2 applications with the help of the @vaadin/angular2-polymer directives.

For this purpose, we show how to create a staff management application similar to the Tour of Heroes example. But unlike in the Angular 2 Tour of Heroes, instead of implementing the UI elements in place, we will use the ready-made UI elements that are built on top of the Web Components technology stack. The resulting application will have a nice-looking mobile-first Material designed UI.

The resulting application source is published on GitHub.

1.1. The Start Point

This tutorial is based on the 5 min QuickStart for Angular 2 and the Angular 2 Tour of Heroes. We start from the QuickStart application source.

If you are not yet familiar with Angular 2, we advice you to first walk through the Angular 2 QuickStart and the Tour of Heroes. In this tutorial, we skip the explanations that are already given in the Angular 2 tutorials.

1.2. The Goal

In our heroes management application, there will be two UI screens. The first screen, as illustrated in The first screen with the list of heroes, uses Vaadin Grid to show a list of all the heroes.

heroes list default
Figure 1. The first screen with the list of heroes

By clicking on a grid row, the user can open the hero details editor screen. It is made using paper-input and vaadin-date-picker:

hero details
Figure 2. The second screen with the hero details view

Both screens have a context-sensitive toolbar at the top, containing the title and the optional back icon button.

2. Creating Project

We begin by creating a new project based on the source code from the Angular 2 QuickStart project. You can either clone it from the GitHub repository or extract it from a ZIP package, as described next.

If you are not yet familiar with Angular 2, please go through the Angular 2 QuickStart tutorial, which explains every step of starting a new Angular 2 application from scratch.

2.1. Starting from the QuickStart Repository

Clone the Angular 2 QuickStart repository into the tutorial project folder:

$ git clone https://github.com/angular/quickstart my-project
$ cd my-project

We are not going to contribute to the quickstart repository itself, so the original repository data is not needed. Remove the .git repository data folder.

2.2. Starting from the QuickStart ZIP Package

Alternatively, instead of using Git to clone the QuickStart repository, you can download and extract the QuickStart zip package.

2.3. Remove the AppComponent Tests

The QuickStart respository contains tests for the AppComponent class. Unfortunately, some changes that will be done to AppComponent in this tutorial would break the compilation of these tests.

Please remove the app/app.component.spec.ts file to prevent compilation errors later in this tutorial.

Testing in Angular 2

Testing is out of the scope of this tutorial.

See the Testing chapter in the Angular 2 Developer Guide for more information on the topic.

2.4. Installing npm Packages and Starting the Development Server

Install npm dependencies:

$ npm install

At this point, you should be able to compile the TypeScript source code and launch the development server. You can start the server to check that everything is fine:

$ npm start

Press Ctrl+C to stop the development server.

See the Angular 2 QuickStart README for more information about creating a new project and other useful npm commands.

3. Adding and Installing Dependencies

After the previous step, we have an empty Angular 2 application source with all the Angular dependencies installed. In this step, we are going to add the Polymer library and some elements as dependencies of our application, and install them.

Vaadin Elements and other Polymer elements are mainly distributed through Bower. We are going to use Bower to declare these dependencies and install them.

3.1. Adding Bower Dependencies

You should install Bower before we start using it. Use npm to install Bower with this command:

$ npm install -g bower

Create the bower.json file in your project root with the following contents:

bower.json
{
  "name": "my-project",
  "description": "",
  "main": "",
  "authors": [
    "Your Name"
  ],
  "license": "ISC",
  "homepage": "",
  "private": true,
  "ignore": [
    "*/.",
    "node_modules",
    "bower_components",
    "test"
  ],
  "dependencies": {
    "polymer": "Polymer/polymer#^1.4.0",
    "iron-flex-layout": "PolymerElements/iron-flex-layout#^1.3.1",
    "iron-icons": "PolymerElements/iron-icons#^1.1.3",
    "app-layout": "PolymerElements/app-layout#^0.9.0",
    "paper-styles": "PolymerElements/paper-styles#^1.1.4",
    "paper-icon-button": "PolymerElements/paper-icon-button#^1.1.1",
    "paper-input": "PolymerElements/paper-input#^1.1.11",
    "vaadin-grid": "Vaadin/vaadin-grid#^1.1.0",
    "vaadin-date-picker": "Vaadin/vaadin-date-picker#^1.1.0"
  }
}

The bower.json file declares all Bower dependencies for our application. Now install them with the following command:

$ bower install

After that, you should have a bower_components directory in your project root, with all elements declared in the bower.json file, and their requirements. List the contents of the bower_components directory to verify that it contains the following subdirectories:

Contents of the bower_components Directory
app-layout
font-roboto
iron-a11y-announcer
iron-a11y-keys-behavior
iron-autogrow-textarea
iron-behaviors
iron-checked-element-behavior
iron-dropdown
iron-fit-behavior
iron-flex-layout
iron-form-element-behavior
iron-icon
iron-icons
iron-iconset-svg
iron-input
iron-media-query
iron-meta
iron-overlay-behavior
iron-resizable-behavior
iron-scroll-target-behavior
iron-selector
iron-validatable-behavior
neon-animation
paper-behaviors
paper-button
paper-icon-button
paper-input
paper-material
paper-ripple
paper-styles
polymer
vaadin-date-picker
vaadin-grid
web-animations-js
webcomponentsjs
Ignore the Bower Dependencies in a Version Control System

It is usually a good practice to exclude external dependencies from a version control system. In this tip, we assume that you plan to use Git, though the same principle applies to any version control system.

The Angular 2 QuickStart project already contains a .gitignore file, that will exclude the node_modules directory with the npm dependencies.

You can add the following line to the .gitignore file to prevent the Bower dependencies from being tracked by Git in future:

bower_components

3.2. The npm Dependency

Alongside with Bower dependencies, we also need to add one npm dependency to the project. The @vaadin/angular2-polymer package adds support for Polymer elements in Angular 2 component templates. Run the following command to install the package and save the dependency in package.json at the same time:

$ npm install @vaadin/angular2-polymer --save

4. Adding Polymer Elements to Our Application

In the previous step, we downloaded the needed elements to the bower_components directory. Next, we will import these elements in our application.

In your project root, edit the index.html file and replace the contents with the following lines:

<!DOCTYPE html>
<html>
  <head>
    <title>Angular 2 with Polymer Elements QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- Polyfills -->
    <script src="bower_components/webcomponentsjs/webcomponents.min.js"></script>
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script>
      window.Polymer = {
        dom: 'shadow'
      };
    </script>

    <!-- JavaScript libraries -->
    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/reflect-metadata/Reflect.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <!-- Styles -->
    <link rel="import" href="bower_components/iron-flex-layout/iron-flex-layout.html">
    <link rel="import" href="bower_components/paper-styles/color.html">
    <link rel="import" href="bower_components/paper-styles/default-theme.html">
    <link rel="import" href="bower_components/paper-styles/typography.html">
    <link rel="import" href="bower_components/paper-styles/shadow.html">
    <style is="custom-style">
      body {
        @apply(--layout-fullbleed);
        @apply(--paper-font-body1);
        background: var(--primary-background-color);
        color: var(--primary-text-color);
      }
    </style>

    <!-- Polymer Elements -->
    <link rel="import" href="bower_components/iron-icons/iron-icons.html">
    <link rel="import" href="bower_components/app-layout/app-layout.html">
    <link rel="import" href="bower_components/paper-icon-button/paper-icon-button.html">
    <link rel="import" href="bower_components/paper-input/paper-input.html">
    <link rel="import" href="bower_components/vaadin-grid/vaadin-grid.html">
    <link rel="import" href="bower_components/vaadin-date-picker/vaadin-date-picker.html">

    <!-- SystemJS Configuration -->
    <script src="systemjs.config.js"></script>
    <script>
      document.addEventListener('WebComponentsReady', function() {
        System.import('app').catch(function(err){ console.error(err); });
      });
    </script>
  </head>

  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

We have made the following important changes to the original Angular 2 tutorial file:

The Doctype Declaration

We added the <!DOCTYPE html> declaration in the first line of the HTML file. It switches document to use the Standards mode, as required by the internals of vaadin-grid.

The Web Components Polyfill

The technology stack behind Web Components (namely, HTML Imports, Shadow DOM, and Custom Elements) is not yet natively supported in all browsers. Therefore, we added the webcomponents.min.js polyfill.

Configure Polymer DOM

By default, Polymer uses shady DOM, which is not compatible with Angular. We created the Polymer global settings object and set it to use shadow DOM instead.

Importing Polymer Elements

We added imports of Polymer elements that we are going to use in our application to the head section of the index.html file.

SystemJS App Import Change

In some browsers, HTML Imports are loaded asynchronously, but we need them to be completely loaded before we import our Angular application. Hence we wrapped the System.import('app')…​ call in the listener callback of the WebComponentsReady event, which is fired by the polyfill after all imports are loaded and elements have been registered.

Load Order

The order in which the Polymer elements and the rest of the Angular application code are loaded does matter. It is required that the Polymer elements are loaded and registered before importing the Angular application. The @vaadin/angular2-polymer package strictly depends on that.

Style Changes

Polymer elements come with nice built-in styles in the way of Material Design. Angular 2 also provides style encapsulation mechanisms for our application components.

Therefore, the global styles are not needed anymore. We removed the external styles.css stylesheet and replaced it with iron-flex-layout and paper-styles style mixin imports and an embedded global style rule for the body element.

The body style rule is the only global style rule that remains in our application. The body element needs to be styled to take the full height of the browser viewport, and also to specify default font styles, line height, background, and text colors.

Instead of figuring out the exact rules and values for the body style, we import and reuse CSS mixins and CSS custom properties declared in iron-flex-layout and paper-styles.

When using custom CSS mixins and custom CSS properties in your main document styles, you need to wrap your styles inside a <style is="custom-style"></style> tag.

See the Styling section of the Polymer Developer Guide for more information on styling Polymer elements and the document, custom CSS mixins and properties usage and limitations.

Delete the styles.css file from your project directory since it is no longer in use.

Duplicated Imports

The HTML imports we have in the index.html file also import any possible dependencies. Imports having the same dependencies result in multiple imports of the same file. In such case, browsers prevent fetching the same file multiple times by checking the file location.

See Fetching Import section of the HTML Imports Spec for more detailed information about the fetching algorithm.

5. Building the Application Layout with Paper Elements

After the previous step, we have some Polymer elements imported in the index.html file of our application. In this step, we are going to use them to create an application layout with a toolbar in AppComponent.

5.1. Updating SystemJS Configuration

For using Polymer elements in our Angular components, we need to import the PolymerElement directives from @vaadin/angular2-polymer. Therefore, we need to make the module loader (SystemJS, in our case) aware of how to load the @vaadin/angular2-polymer package.

Angular 2 TypeScript QuickStart contains the SystemJS configuration in the systemjs.config.js file in the project root. Please edit this file and add a mapping for the @vaadin scope and the @vaadin/angular2-polymer package there as follows:

systemjs.config.js
/**
 * System configuration for Angular 2 samples
 * Adjust as necessary for your application needs.
 */
(function(global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'app',

      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',

      // other libraries
      'rxjs':                       'npm:rxjs',
      'angular2-in-memory-web-api': 'npm:angular2-in-memory-web-api',
      '@vaadin/angular2-polymer':   'npm:@vaadin/angular2-polymer'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      },
      'angular2-in-memory-web-api': {
        main: './index.js',
        defaultExtension: 'js'
      },
      '@vaadin/angular2-polymer': {
        main: './index.js',
        defaultExtension: 'js'
      }
    }
  });
})(this);

5.2. Extending AppModule with Polymer Elements

Now we import the PolymerElement directives and attach them to the AppModule, in order to allow using the listed Polymer elements in this Angular module. Edit app/app.module.ts, append the PolymerElement directives to the declarations list, and add the CUSTOM_ELEMENTS_SCHEMA, as follows:

app/app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from [email protected]/core';
import { BrowserModule } from [email protected]/platform-browser';
import { PolymerElement } from [email protected]/angular2-polymer';

import { AppComponent }  from './app.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    AppComponent,
    PolymerElement('app-header-layout'),
    PolymerElement('app-header'),
    PolymerElement('app-toolbar')
  ],
  bootstrap: [ AppComponent ],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }

5.3. AppComponent Changes

Open app/app.component.ts and replace the contents with the following code:

app/app.component.ts
import { Component } from [email protected]/core';

@Component({
  selector: 'my-app',
  template: `
    <app-header-layout has-scrolling-region>
      <app-header fixed>
        <app-toolbar>
          <div title spacer>All heroes</div>
        </app-toolbar>
      </app-header>
      <div>My application content</div>
    </app-header-layout>
  `,
  styles: [`
    app-toolbar {
      background: var(--primary-color);
      color: var(--dark-theme-text-color);
    }
  `]
})
export class AppComponent { }

Save the changes and launch the development server to see the results in your browser. After loading, your application should look as follows:

app layout
Figure 3. The empty application layout

Now your application has a layout made by using the app-header-layout, app-header, and app-toolbar elements.

5.4. Elements Used in This Step

app-header-layout

The application layout that consists of the app-header element and the main contents. In our case, it adds a scrollable container for the application contents as well.

app-header

Acts as a header in the application layout. The header is fixed in our application.

app-toolbar

Provides a toolbar wrapper.

The app-layout Elements are Design-Agnostic

Polymer elements from the app-layout set, including app-toolbar that we use, are design-agnostic. They do not have Material Design look by default. We need to adjust app-toolbar styles a bit.

Therefore, we added color rules for the app-toolbar in the styles of the AppComponent. We reuse the color values of default theme from paper-styles.

Apart from the colors, it inherits the font family declared for the body. We have already declared our font settings for the body in the index.html file earlier during this step.

The PolymerElement Directives

In order to enable all features of Polymer elements used inside your Angular component templates, remember to import PolymerElement in the module file and add PolymerElement('element-name') line for each Polymer element that you use to the declarations array of your Angular module metadata.

6. List Heroes with Vaadin Grid

In the previous step, we added the application layout with app-layout elements. Next, we are going add actual application content. Our plan is to use Vaadin Grid to list Heroes.

Some Parts are Explained in the Tour of Heroes

This step partly follows the Angular 2 Tour of Heroes Tutorial. Therefore, we skip explaining the parts of the code that are similar in both this tutorial and the Tour of Heroes, such as the Hero class and the HeroService.

See Tour of Heroes for a detailed explaination of such similar parts.

6.1. Hero Class

Let us start by creating the Hero class. Create a app/hero.ts file with the following contents:

app/hero.ts
export class Hero {
  id: number;
  name: string;
  birthday: string; // Using strings for simplicity
}

Unlike in Angular 2 Tour of Heroes, in our application we store and expose the birthday of each hero for the user. Here we add the birthday: string; property to our Hero class.

Using Strings to Store Dates

Why are we using the string type and not Date to store dates? There are two reasons:

  1. The built-in JavaScript Date type is always stored as a timestamp, so it always contains the exact time information. This is not only redundant, but also harder to use than a plain string in case of storing just a date. It requires extra care about the correct time and timezone when storing the value and displaying it to the user; otherwise we might get incorrect dates because of timezone mismatches.

  2. vaadin-date-picker, as well as native HTML5 <input type="date">, gives the date value as an ISO-formatted string. To preserve the simplicity in our application, we also store dates as strings, avoiding conversions.

6.2. Mock Heroes Data

Create a app/mock-heroes.ts file with some heroes data:

app/mock-heroes.ts
import { Hero } from './hero';

export var HEROES: Hero[] = [
  { "id": 11,  "name": "Mr. Nice",   "birthday": "1980-01-11" },
  { "id": 12,  "name": "Narco",      "birthday": "1980-01-12" },
  { "id": 13,  "name": "Bombasto",   "birthday": "1980-01-13" },
  { "id": 14,  "name": "Celeritas",  "birthday": "1980-01-14" },
  { "id": 15,  "name": "Magneta",    "birthday": "1980-01-15" },
  { "id": 16,  "name": "RubberMan",  "birthday": "1980-01-16" },
  { "id": 17,  "name": "Dynama",     "birthday": "1980-01-17" },
  { "id": 18,  "name": "Dr IQ",      "birthday": "1980-01-18" },
  { "id": 19,  "name": "Magma",      "birthday": "1980-01-19" },
  { "id": 20,  "name": "Tornado",    "birthday": "1980-01-20" }
];

6.3. The Hero Service

We also need a HeroService to be able to retrive the heroes list in our Angular application. Create a app/hero.service.ts file:

app/hero.service.ts
import { Injectable } from [email protected]/core';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes() {
    return Promise.resolve(HEROES);
  }
}

6.4. Heroes List Component

Add the heroes list component file app/heroes.component.ts with the following code:

app/heroes.component.ts
import { Component, OnInit } from [email protected]/core';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  template: `
    <vaadin-grid [items]="heroes">
      <table>
        <colgroup>
          <col name="id">
          <col name="name">
          <col name="birthday">
        </colgroup>
      </table>
    </vaadin-grid>
  `,
  styles: [`
    vaadin-grid {
      height: 100%;
    }
  `]
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];

  constructor(private _heroService: HeroService) { }

  getHeroes() {
    this._heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit() {
    this.getHeroes();
  }
}

Here in the HeroesComponent, we have the vaadin-grid element in the template. In the styles, we have a height: 100%; rule for the vaadin-grid element. In the template, there are three columns inside the vaadin-grid, specified with their corresponding item property names.

Also in the template, the items property of the vaadin-grid is bound to the heroes array property of HeroesComponent. At the same time, we import and use HeroService to get the list of heroes and assign the heroes property. Data binding of the Angular component takes care of updating the items property of vaadin-grid with the list of heroes for us.

6.5. Displaying Heroes List

Next, we need to edit the app/app.component.ts file to display the heroes list in the AppComponent template. Replace <div>My application content</div> with <my-heroes></my-heroes>, as in the following code:

app/app.component.ts
import { Component } from [email protected]/core';

@Component({
  selector: 'my-app',
  template: `
    <app-header-layout has-scrolling-region>
      <app-header fixed>
        <app-toolbar>
          <div title spacer>All heroes</div>
        </app-toolbar>
      </app-header>
      <my-heroes></my-heroes>
    </app-header-layout>
  `,
  styles: [`
    app-toolbar {
      background: var(--primary-color);
      color: var(--dark-theme-text-color);
    }
  `]
})
export class AppComponent { }

6.6. Updating AppModule

Finally in this step, we update the AppModule. Change the contents of app/app.module.ts as follows:

app/app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from [email protected]/core';
import { BrowserModule } from [email protected]/platform-browser';
import { PolymerElement } from [email protected]/angular2-polymer';

import { AppComponent }  from './app.component';
import { HeroService } from './hero.service';
import { HeroesComponent } from './heroes.component';

@NgModule({
  imports: [ BrowserModule ],
  declarations: [
    AppComponent,
    PolymerElement('app-header-layout'),
    PolymerElement('app-header'),
    PolymerElement('app-toolbar'),
    PolymerElement('paper-icon-button'),
    HeroesComponent,
    PolymerElement('vaadin-grid')
  ],
  providers: [ HeroService ],
  bootstrap: [ AppComponent ],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }

We did the following changes in the app/app.module.ts file:

  • We imported HeroService and listed it in providers

  • We also imported HeroesComponent, and appended it the declarations, alongside with the PolymerElement directives for the vaadin-grid element, which is used in the HeroesComponent template.

Now it is again time to look in the browser window, to see how the heroes list looks in our application. It should look about as in The list of heroes:

heroes list
Figure 4. The list of heroes

7. Hero Editor and Routing

Previously we added the heroes list in our application. In this step, we are going to add the editing feature. After that, the user should be able to navigate to the hero details by clicking a row in the heroes list, edit the details, and get back to the list with the back button in the toolbar.

Some Parts are Explained in the Tour of Heroes

This step partly follows the Angular 2 Tour of Heroes Tutorial. Therefore, we skip explaining the parts of the code that are similar both in this tutorial and in the Tour of Heroes, such as the routing requirements and configuration.

See Tour of Heroes for a detailed explaination of the similar parts.

7.1. Add the Hero Get Method to the Service

Let us add a getHero(id: number) method to the HeroService. It can be used to retrive a single hero in our application components. Open the app/hero.service.ts file and change its contents to the following code:

app/hero.service.ts
import { Injectable } from [email protected]/core';

import { Hero } from './hero';
import { HEROES } from './mock-heroes';

@Injectable()
export class HeroService {
  getHeroes() {
    return Promise.resolve(HEROES);
  }

  getHero(id: number) {
    return Promise.resolve(HEROES).then(
      heroes => heroes.filter(hero => hero.id === id)[0]
    );
  }
}

7.2. Add the Hero Editor Component

Create a file named app/hero-detail.component.ts and put the following lines in it:

app/hero-detail.component.ts
import { Component, OnInit } from [email protected]/core';
import { ActivatedRoute } from [email protected]/router';
import { Subscription } from 'rxjs/Subscription';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-hero-detail',
  template: `
    <div *ngIf="hero">
      <paper-input label="Name" [(value)]="hero.name"></paper-input>
      <vaadin-date-picker label="Birthday" [(value)]="hero.birthday"></vaadin-date-picker>
    </div>
  `,
  styles: [`
    :host {
      display: block;
      padding: 16px;
    }
  `]
})
export class HeroDetailComponent implements OnInit {
  hero: Hero;
  private _routeParamsSubscription: Subscription;

  constructor(
    private _route: ActivatedRoute,
    private _heroService: HeroService
  ) { }

  ngOnInit() {
    this._routeParamsSubscription = this._route.params.subscribe(params => {
      let id = +params['id']; // (+) converts string 'id' to a number
      this._heroService.getHero(id).then(hero => this.hero = hero);
    });
  }

  ngOnDestroy() {
    this._routeParamsSubscription.unsubscribe();
  }
}

So, here we have just created HeroDetailComponent, the heroes editor for our application. It uses paper-input bound to the hero.name and vaadin-date-picker bound to the hero.birthday property through two-way data binding in both cases (that is, with the [(value)] syntax).

HeroDetailComponent gets the hero ID from the ActivatedRoute params and calls getHero(id: number) method from HeroService with the hero ID argument to retrive the hero object. After the retrieval, the hero object is assigned to the hero property of HeroDetailComponent.

Since we use two-way binding, the hero.name and the hero.birthday sub-property values are automatically displayed in the corresponding elements. When the user edits these values in the elements, the sub-properties of the hero property are updated automatically.

Use ngIf When Loading Content

The hero object is retrived asynchronously after the component initialization. At this time when the retrieval starts, the component template is already rendered, but the hero is not loaded yet, so we can not use hero.name and hero.birthday sub-properties. Using them at this time would result in errors.

That is why we wrap the paper-input and the vaadin-date-picker elements with <div *ngIf="hero"></div> in the component template. The ngIf structural directive not only hides the content, but also stops the hidden part of the template from being evaluated and rendered. This effectively prevents errors of accessing non-existant sub-properties during the loading.

Unlike with Vaadin Grid in the heroes list, we do not want our editor contents to touch the edges of the browser window. It is nice to have some spacing around them. For that reason, we add display: block; and padding: 16px; rules in the styles section of our component metadata.

7.3. Add Routing

The Angular 2 Component Router uses history.pushState API for navigation. This requires us to declare the base href for the main document. Add this line to the index.html file in the project root just after the head opening tag:

index.html
...
<head>
  <base href="/">
  ...
</head>
...

Next we create a router configuration file. Add app/app.routing.ts with the following contents:

app/app.routing.ts
import { Routes, RouterModule } from [email protected]/router';

import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';

const appRoutes: Routes = [
  {
    path: '',
    redirectTo: '/heroes',
    pathMatch: 'full',
  },
  {
    path: 'heroes',
    children: [
      {
        path: '',
        component: HeroesComponent,
        data: {
          title: 'All heroes',
          root: true
        }
      },
      {
        path: ':id',
        component: HeroDetailComponent,
        data: {
          title: 'Hero detail'
        }
      }
    ]
  }
];

export const appRoutingProviders: any[] = [

];

export const routing = RouterModule.forRoot(appRoutes);

The routes list starts with the default route, which corresponds to the empty path. This route is used when no path is specified, and in our configuration, it redirects users to the /heroes path to open the heroes list by default.

After that in our routing configuration, there are two routes grouped as children under the /heroes path: one route is for the heroes list (HeroesComponent) and another is for the hero detail editor (HeroDetailComponent). Note, that the second route path features the :id parameter. It is received inside HeroDetailComponent and used there to retrive the hero object, as described above in this step.

Next, add a router outlet, the back button, and the navigation reaction to the AppComponent. Edit app/app.component.ts to contain the code below:

app/app.component.ts
import { Component, OnInit } from [email protected]/core';
import { ActivatedRoute, Router, NavigationEnd } from [email protected]/router';
import { Subscription } from 'rxjs/Subscription';

@Component({
  selector: 'my-app',
  template: `
    <app-header-layout has-scrolling-region>
      <app-header fixed>
        <app-toolbar [class.raised]="isInChildView">
          <paper-icon-button icon="arrow-back" *ngIf="isInChildView" (click)="goBack()"></paper-icon-button>
          <div title spacer>{{title}}</div>
        </app-toolbar>
      </app-header>
      <router-outlet></router-outlet>
    </app-header-layout>
  `,
  styles: [`
    app-toolbar {
      background: var(--primary-color);
      color: var(--dark-theme-text-color);
    }

    app-toolbar.raised {
      @apply(--shadow-elevation-4dp);
    }

    paper-icon-button {
      position: absolute;
      top: 12px;
      left: 8px;
    }
  `]
})
export class AppComponent implements OnInit {
  title = '';
  isInChildView = false;
  private _routerSubscription: Subscription;

  constructor(private _route: ActivatedRoute,
              private _router: Router) { }

  ngOnInit() {
    this._routerSubscription = this._router.events.subscribe(event => {
      if (event instanceof NavigationEnd) {
        let route = this._route.snapshot;
        while (route.firstChild) {
          route = route.firstChild;
        }
        this.title = route.data['title'];
        this.isInChildView = !route.data['root'];
      }
    });
  }

  ngOnDestroy() {
    this._routerSubscription.unsubscribe();
  }

  goBack() {
    this._router.navigate(['/heroes']);
  }
}

Then we need to update the AppModule, to import and enable the hero detail editor and the routing configuration. Change the app/app.module.ts file contents as follows:

app/app.module.ts
import { NgModule, CUSTOM_ELEMENTS_SCHEMA } from [email protected]/core';
import { BrowserModule } from [email protected]/platform-browser';
import { PolymerElement } from [email protected]/angular2-polymer';

import { AppComponent }  from './app.component';
import { HeroService } from './hero.service';
import { HeroesComponent } from './heroes.component';
import { HeroDetailComponent } from './hero-detail.component';
import { routing, appRoutingProviders } from './app.routing';

@NgModule({
  imports: [
    BrowserModule,
    routing
  ],
  declarations: [
    AppComponent,
    PolymerElement('app-header-layout'),
    PolymerElement('app-header'),
    PolymerElement('app-toolbar'),
    PolymerElement('paper-icon-button'),
    HeroesComponent,
    PolymerElement('vaadin-grid'),
    HeroDetailComponent,
    PolymerElement('paper-input'),
    PolymerElement('vaadin-date-picker')
  ],
  providers: [
    HeroService,
    appRoutingProviders
  ],
  bootstrap: [ AppComponent ],
  schemas: [ CUSTOM_ELEMENTS_SCHEMA ]
})
export class AppModule { }

The last feature to implement in this step is navigation from the heroes list to the hero details. Open app/heroes.component.ts and change it to contain the following code:

app/heroes.component.ts
import { Component, OnInit } from [email protected]/core';
import { Router } from [email protected]/router';

import { Hero } from './hero';
import { HeroService } from './hero.service';

@Component({
  selector: 'my-heroes',
  template: `
    <vaadin-grid [items]="heroes" (selected-items-changed)="onSelectedItemsChanged($event)">
      <table>
        <colgroup>
          <col name="id">
          <col name="name">
          <col name="birthday">
        </colgroup>
      </table>
    </vaadin-grid>
  `,
  styles: [`
    vaadin-grid {
      height: 100%;
    }
  `]
})
export class HeroesComponent implements OnInit {
  heroes: Hero[];

  constructor(
    private _router: Router,
    private _heroService: HeroService
  ) { }

  getHeroes() {
    this._heroService.getHeroes().then(heroes => this.heroes = heroes);
  }

  ngOnInit() {
    this.getHeroes();
  }

  onSelect(hero: Hero) {
    this._router.navigate(['/heroes', hero.id]);
  }

  onSelectedItemsChanged(event: any) {
    let selectedIndex: number = event.target.selection.selected()[0];
    if (selectedIndex !== undefined) {
      this.onSelect(this.heroes[selectedIndex]);
    }
  }
}

Now when the user clicks a row inside the heroes list, vaadin-grid fires selected-items-changed event. We bound the event to the onSelectedItemsChanged(event: any) method of the HeroesComponent. In the listener method, we read the selected item index, find the selected heroes array item, and call onSelect(hero: Hero), which uses Router to navigate to the hero detail editor for the selected hero.

7.5. Try It Out

All the changes for this step are done. Now launch your application again and try how the navigation works.

After opening the application, click the first row in the heroes list. You should see the details view like in the following screenshot:

hero details
Figure 5. The hero detail view

Click the back icon in the toolbar to navigate back to the heroes list. If you made any changes in the hero detail editor, they should be shown in the heroes list right away.

7.6. Nice Touches in the AppComponent

In the folllowing, we go through and explain all the UX-related changes that were made to the AppComponent class earlier.

7.6.1. Dynamic Toolbar Title

We added the title property to the AppComponent and bound it to the text content of <div title spacer></div> inside the toolbar in the template.

Instead of a static title, the title is now updated dynamically. We subscribed to the Router events in AppComponent and used route data in the navigation event callback to get the title value specified for the current route. Each time after user opens the application or navigates inside, the Router NavigationEnd event is dispatched, so that the title property will be updated.

7.6.2. The Back Icon in the Toolbar

We added paper-icon-button to have a back icon inside the app-toolbar in the template. The icon has a click event binding, which calls the goBack() method of the AppComponent class. In the method, we invoke the navigate method of the Router to navigate back to the heroes list from the hero details.

When the heroes list is shown, the back icon is useless, so we need to hide it. To achieve that, we added isInChildView property to AppComponent, which is updated from the route data in the navigation event callback. In the template, we added *ngIf="isInChildView" for the paper-icon-button.

We also added a few positioning style rules for the paper-icon-button.

7.6.3. Dynamic Toolbar Shadow

To make the toolbar look better, we made the application toolbar to have a shadow that is shown only for the hero detail view, but not for the heroes list view. For this purpose, we bound the raised class of the app-toolbar to isInChildView property and added a style rule which applies the shadow mixin from paper-styles to the app-toolbar when it has the raised class.

8. Wrap Up

You have now finished all the tutorial steps and know how to use all the powers of Polymer elements in your Angular 2 applications.

You can see the resulting application source on GitHub.

8.1. Further Steps

There are several ways to improve the application you created in this tutorial. For example, you might want to add an explicit Save button to the hero detail editor and make the user able to intentionally submit or discard their changes. To do that, you can use the ngForm directive. See the Forms Chapter in the Angular 2 Basics guide for the detailed instructions.

In the tutorial, we did not consider the topic of storing your application data. For simplicity, our application uses mock in-memory data and relies on data binding to make temporary changes, which are not saved anywhere. You might want to move the heroes data to a server and add some HTTP API calls in your application. See the Angular 2 Http Client documentation to know how to do that.

Read the Polymer Project website to know about other features that Polymer provides you with. There is also the Getting Started guide, where you can learn how to create your own elements and apps based on Polymer.

Don’t forget to find out abouth other elements that you can use for building your applications from the Vaadin Elements page and the Polymer Catalog.