AngularJS 2

first impressions by @GionKunz

A Glance Into the Future

Gion Kunz

Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

Become a contributor!

Close ad

We'll Cover the following

  • Core Concepts
  • New Syntax
  • New Template Syntax
  • Directives & Components
  • Dependeny Injection
  • Writing future proof code

We won't cover

  • Router, Forms etc.
  • Testing and Mocking
  • E2E Testing
  • Core Services (XHR etc.)
  • Number crunching

THE EVOLUTION OF WEB STANDARDS

THE MODERN WEB

Object.observe()

ECMAScript 6

Web Components

the three main web standards behind AngularJS 2

OBSERVER

Object.observe()

var object = {};
Object.observe(object, function(changeRecords) {
  changeRecords.forEach(function(cr) {
    console.log(cr.name + ' ' + cr.type);
  });
});
 
object.p1 = 'Property 1';
object.p2 = 'Property 2';
object.p2 = 'Property II';
delete object.p1;

The future
iS

ES6

NOW

ES6 Class

class Person {
  constructor(name) {
    this.name = name;
  }
  
  sayHello() {
    console.log(this.name + ': Hi there!');
  }
  
  static createPerson(name) {
    return new Person(name);
  }
}

var gion = new Person('Gion');
gion.sayHello();

ES6 Class DESUGARED

function Person(name) {
  this.name = name;
}

Person.prototype.sayHello = function sayHello() {
  console.log(this.name + ': Hi there!');
};

Person.createPerson = function createPerson(name) {
  return new Person(name);
};

var gion = new Person('Gion');
gion.sayHello();

ES6 Class Inheritance

class Organism {}

class Person extends Organism {
  constructor(name) {
    super();
    this.name = name;
  }
}

class Developer extends Person {
  constructor(name, lovesAngular) {
    super(name);
    this.lovesAngular = lovesAngular;
  }
}

var gion = new Developer('Gion', true);
console.log(gion);

ES6 Class Inheritance DESUGARED

let's skip this...

ES6 TEMPLATE STRINGS

var num = 100;
var str = `The square root of ${num} is ${Math.sqrt(num)}`;
console.log(str);

var className = 'my-class';
var title = 'Welcome';
var html = `
  <div class="${className}">
    <h1>${title}</h1>
  </div>
`;
console.log(html);

WITHOUT ES6 TEMPLATE STRINGS

var num = 100;
var str = 'The square root of ' + num + ' is ' + Math.sqrt(num);
console.log(str);

var className = 'my-class';
var title = 'Welcome';
var html = '\n' +
  '<div class="' + className + '">\n' +
  '  <h1>' + title + '</h1>\n' +
  '</div>\n';
console.log(html);

Many more super cool features!

Use ES6

TODAY!

WEB COMPONENTS

</>

The Standard

Custom Elements

HTML Imports

Templates

Shadow DOM

Templates

<body>
<template id="template">
  <h1>This is a template!</h1>
</template>
</body>
var template = document.querySelector('#template');
var instance = document.importNode(template.content, true);
document.body.appendChild(instance);

Shadow DOM

<body>
<div id="host"></div>
</body>
var host = document.querySelector('#host');
var root = host.createShadowRoot();
var elem = document.createElement('h1');
elem.textContent = 'Bombaclat!';
root.appendChild(elem);

Bored?

One more thing!

ES6 was not enough...

Type annotations

  • Availability at runtime
  • Framework support (DI)
  • Better tooling
  • Runtime type assertion
  • Can improve code quality
  • Code optimization (once standardized)

Meta annotations

  • Availability at runtime
  • Declarative
  • Famework support
  • Better tooling (once stabilized)
  • Less and more explicit code
  • Are proposed to the standard as "decorators"

Robot Drumroll...

Are you ready?

AngularJS 2

Warning!
Under heavy construction

Hello World

Meta Annotations

Template Strings

Type Annotations

@Component({
  selector: 'app'
})
@View({
  template: `
    <div>
      <h1>Hello {{ name }}!</h1>
    </div>
  `
})
class App {
  name:string;

  constructor() {
    this.name = 'World';
  }
}

Desugaring TO ES5*

desugaring from AtScript before moving to TypeScript with ES7 like decorators *

function App() {
  this.name = 'World';
}

App.annotations = [
  new Component({
    selector: 'app'
  }),
  new View({
    template: 
      '<div>\n' +
      '  <h1>Hello {{ name }}!</h1>\n' +
      '</div>\n'
  })
];

Architecture

AngularJS 1

View

Controller

Directive

Model

Model

Controller

View

Provider

Value

Factory

Constant

Service

DI

AngularJS 2

Directive

Component

View

Injectable

<<*async>>

<<any>>

Bindings

ELEMENT Property bindings

@Component({
  selector: 'counter'
})
@View({
  template: '<input type="text" [value]="num">'
})
class App {
  num:number = 0;

  constructor() {
    setInterval(function() {
      this.num++;
    }.bind(this), 1000);
  }
}

[square brackets]

EVENT Bindings

(round brackets)

@Component({
  selector: 'app'
})
@View({
  template: `
    <button (click)="onClick()">Click me</button>
  `
})
class App {
  onClick() {
    alert('Cool!');
  }
}

More EVENT Bindings

@Component({
  selector: 'app'
})
@View({
  template: `
    <input id="inp" type="text"
           (keyup)="onKeyup($event)" (seven)="onSeven()">
  `
})
class App {
  onKeyup(event) {
    if(event.currentTarget.value.length === 7) {
      event.currentTarget.dispatchEvent(new Event('seven'));
    }
  }
  onSeven() {
    alert('Cool!');
  }
}

To sum it up

(event-binding)="expression"

[property-binding]="expression"

No 2-way bindings

Good old Directives

@Directive({
  selector: 'input[pass]',
  events: ['passMatch'],
  hostListeners: { 
    keyup: 'onKeyup($event.target.value)' 
  }
})
class PassInput {
  passPhrase:string = 'test123';
  passMatch:EventEmitter 
    = new EventEmitter();
  
  onKeyup(value) {
    if(value === this.passPhrase) {
      this.passMatch.next('matched');
    }
  }
}
import PassInput from './pass-input.js';


@Component({
  selector: 'app'
})
@View({
  template: `
    <input type="text"
           (pass-match)="onPassMatch($event)">
    Matched: {{count}}`,
  directives: [PassInput]
})
class App {
  constructor() { this.count = 0; }
  onPassMatch(event) {
    this.count++;
  }
}

App Component

Directive

Everything is a Directive!

Everything is a Directive!

Dependency injection

Injection resolution stages

pre-existing injectors

component injectors

element injectors

  • The terminal injector throws exception or resolves @Optional to null
  • The platform injector resolves browser singleton resources like cookies and location
  • Each component has its own injector
  • Inherited through DOM order of nested components
  • Each element within the shadow DOM of a component has its own injector
  • Inherited through DOM order of nested elements

Simple injection

class ListService {
  list:Array<string> = ['One', 'Two', 'Three'];
}

@Component({
  selector: 'app',
  injectables: [ListService]
})
@View({
  template: `List: {{ list }}`
})
class App {
  constructor(listService:ListService) {
    this.list = listService.list;
  }
}

Concrete With @Inject

class HelloAbstract {
  hello() { throw new Error('Abstract!'); }
}
class HelloConcrete extends HelloAbstract {
  hello() { return 'Howdy doodie?'; }
}

@Component({
  selector: 'app',
  injectables: [
    HelloConcrete
  ]
})
@View({template: '{{ helloService.hello() }}' })
class App {
  helloService:HelloAbstract;
  constructor(@Inject(HelloConcrete) helloService:HelloAbstract) {
    this.helloService = helloService;
  }
}

With Injector Binding

class HelloAbstract {
  hello() { throw new Error('Abstract!'); }
}
class HelloConcrete extends HelloAbstract {
  hello() { return 'Howdy doodie?'; }
}

@Component({
  selector: 'app',
  injectables: [
    bind(HelloAbstract).toClass(HelloConcrete)
  ]
})
@View({template: '{{ helloService.hello() }}' })
class App {
  helloService:HelloAbstract;
  constructor(helloService:HelloAbstract) {
    this.helloService = helloService;
  }
}

Element Level Injection

@Directive({
  selector: 'button[kamikaze]'
})
class KamikazeButton {
  constructor(elem:NgElement) {
    elem.domElement.addEventListener('click', function(event) {
      event.currentTarget.style.display = 'none';
    }.bind(this));
  }
}

@Component({ selector: 'app' })
@View({
  template: '<button kamikaze>Remove me</button>',
  directives: [KamikazeButton]
})
class App {}

More Element Level Injection

@Directive({
  selector: '[color-button]',
  hostListeners: {click: 'onClick()'}
})
class ColorButton {
  constructor(@Parent() panel:ColorPanel,
              @Attribute('color-button') color:string) {
    this.color = color;
    this.panel = panel;
  }
  onClick() {
    this.panel.bgColor(this.color);
  }
}

@Directive({ selector: 'color-panel' })
class ColorPanel {
  constructor(@PropertySetter('style.background-color') bgColor:Function) {
    this.bgColor = bgColor;
  }
}

Future proof Code

Things need to stabilize first

  • Still very experimental and things are changing a lot
  • Router is not fully ported / documented yet
  • No ngResource alternative yet
  • Forms module has not matured yet

My Personal Opinion
Not proven yet

  • Use ECMAScript 6
  • Make everything a directive
  • Always use controllerAs syntax
  • Avoid $scope.$watch and use reactive approaches
  • Only create services
  • Use ES6 classes for controllers and services
  • Use the new router for 1.4 (angular-new-router)

Thank You!

Gion Kunz

Front End Developer

@GionKunz

gion.kunz@oddeven.ch

https://oddeven.ch

AngularJS 2 - A Glance Into the Future @ SWISSJS

By Gion Kunz

AngularJS 2 - A Glance Into the Future @ SWISSJS

  • 4,426