Angular Application Architecture – Data Binding



Angular Application Architecture – Data Binding

6 11


ForwardJS-Angular2

ForwardJS 2016 - Angular 2.0 Workshop

On Github Yonet / ForwardJS-Angular2

Introduction to Angular 2.0

Created by Aysegul Yonet / twitter@AysegulYonet

Slides: bit.ly/ForwardJS-Angular2

  • Introduce yourself
    • Your name
    • Your Angular 1.x and 2.0 experience
    • Did you create custom Directives with Angular 1.x
  • Node

    Verify that you are running at least node v4.x.x and npm 3.x.x by running node -v and npm -v in a terminal/console window. Older versions produce errors.

  • npm install -g angular-cli

About me

The Plan

  • Go through concepts and tools
  • Basic hello world example in ES6
  • TypeScript
  • Build an app

Go through the basics before lunch, build something real afternoon.

You will learn

  • ES6 / TypeScript
  • Custom Components / Services / Pipes
  • Data / Event binding
  • Passing Data between Component
  • Routing

At the end of the day...

What's new

  • Angular 2.0 rc.6
  • Modern Browsers (Safari 7 & 8, IE10 & 11, Android 4.1+)
  • Web Workers
  • AngularJS 2 is being written in TS/ES6
  • Faster change detection
  • Modular
  • Simplified APIs
  • Flexible router
  • Better integration with external libraries with Zone.js
No more digest cycle, will use Object.observe in the future. You can download as much as you use. Zone allows you to catch the changes outside of Angular by property binding, you don't have to use $scope.apply anymore.

TypeScript compiler

npm install -g typescript
npm install -g angular-cli@latest

Install the compiler and watch for changes to compile your ts code to JS.

TypeScript Playground

You don't have to use everything in TS. if you put public in front of a variable you don't have to bind it to this.

3 basic types in TypeScript

var isDone: boolean = false;
var lines: number = 42;
var name: string = "Aysegul";

var notSure: any = 4;
notSure = "maybe a string instead";
notSure = false;

// For collections, there are typed arrays and generic arrays
let list: number[] = [1, 2, 3];
// Alternatively, using the generic array type
let list: Array<number> = [1, 2, 3];
let projects: ProjectType[] =
export class ProjectType {
    id: number;
    name: string;
    url: string;
}

let projects: ProjectType[] = [
    {id: 1, name: 'Demo project', url: '...'}
]

// "void" is used in the special case of a function returning nothing
function bigHorribleAlert(): void {
  alert("I'm a little annoying box!");
}

var f1 = function(i: number): number { return i * i; }
// Return type inferred
var f2 = function(i: number) { return i * i; }
var f3 = (i: number): number => { return i * i; }
// Return type inferred
var f4 = (i: number) => { return i * i; }
// Return type inferred, one-liner means no return keyword needed
var f5 = (i: number) => i * i;

Functions are first class citizens, support the lambda "fat arrow" syntax and use type inference.The following are equivalent, the same signature will be infered by the compiler, and same JavaScript will be emitted.

Data Binding

<!-- Interpolation -->
<div>Hello, {{name}}!</div>
function chartDirective () {
  return {
    restrict: ‘E’,
    scope: {
      data:"parentData"
    },
    link: link
  };
};

function link () {}
function chartDirective () {
  return {
    restrict: ‘E’,
    scope: {},
    bindToController: {
        data:"parentData"
    }
    link: link
  };
};

function link () {}
<chart data="parentData"></chart>

Data flows into a component via property bindings, through square brackets. Data flows out of a component via event bindings, through parenthesis. Property and event bindings are the public API of a component. You use them when you instantiate a component in your application.

Property binding

<!-- Property Binding -->
<chart [data]="parentData"></chart>
<div [hidden]="hideMessage">I am a message</div>

Event binding

<!-- Event Binding -->
<button (click)="doStuff()">Do Stuff</button>

Plunker

It’s very easy to spot what is binding syntax and what is just a plain old attribute. The [] and () bindings are being tied directly into the component/DOM element properties and events. This means any public DOM event or property can be bound to with no additional coding!

Two way data binding - not really!

<input [(ngmodel)]="message" placeholder="message">

Plunker

$event is the first argument.

Local variable

<input type="text" #ref="">
<p>{{ref.value}}</p>

Plunker

# creates a local variable. ref refers to the node itself.

<!-- Template Binding (ng-for, ng-if, etc...) -->
<ul>
  <li *ngfor="let message of messages">{{message}}</li>
</ul>

Read more!

ngFor is camelcase

Recap

  • [Property] and (event) bindings are the public API of a directive.
  • Data flows into a directive via property bindings.
  • Data flows out of a directive via event bindings.
  • You can use property and event bindings to implement two-way bindings.
  • Angular provides syntax sugar for two-way bindings, interpolation, and passing constants.

Exercise

Plunker

  • Open plunker editor
  • Create a list of projects
  • Repeat the projects using *ngFor
  • Create a click event on repeated project to select the clicked item
  • Add an input field to add new projects

ES6 module syntax

//lib.js
export function square(x) {
 return x * x;
}
// main.js
import { square } from 'lib';

ES6 module syntax

//lib.js
export class Math{
  this.square(){}
}
// main.js
import { Math } from 'lib';

Bootstrapping

//main.ts
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';

bootstrap(AppComponent, []);

Bootstrapping in ES 5

document.addEventListener('DOMContentLoaded', function () {
  ng.platform.browser.bootstrap(MyAppComponent, [MyService, ng.core.provide(...)]);
});

Annotations

@Component({
    selector: 'post-list'
})

Annotations define how we customize our components. Annotations provide the additional information that Angular requires in order to run yourapplication.

ES 5 Annotations

(function(app) {
  app.AppComponent =
  ng.core.Component({
    selector: 'my-app',
    template: '<h1>My First Angular 2 App</h1>'
  })
  .Class({
    constructor: function() {}
  });
})(window.app || (window.app = {}));
//Constructor function. //Annotations is a list attached to function.

Components

<todo-list></todo-list>
<time-picker></time-picker>

Components are small reusable pieces of our UI, so that we can create a whole app without repeating our code.

@Component({
  selector: 'catstagram',
})

export Class Catstagram {
 constructor(){
  this.posts = [];
 }
}
Declare reusable UI building blocks for an application. Each Angular component requires a single @Component and at least one @View annotation. The @Component annotation specifies when a component is instantiated, and which properties and hostListeners it binds to. When a component is instantiated, Angular: creates a shadow DOM for the component. loads the selected template into the shadow DOM. creates all the injectable objects configured with hostInjector and viewInjector.
@Component({
 selector: 'catstagram',
 templateUrl:'posts.html'
})

export Class Catstagram {
 constructor(){
   this.posts = [];
 }
}

@Component({
 selector: 'catstagram',
 templateUrl:'posts.html',
})

export Class Catstagram {
  constructor(){
    this.posts = [];
  }
}

angular
  .module('app')
  .directive('catstagram', catstagramComponent);

  function catstagramComponent () {
     var directive =  {
       scope: {
         data:'=data'
       },
       link: createComponent
     }

     return directive;

      function createComponent () {}
}

angular
  .module('app')
  .directive('catstagram', catstagramComponent);

    function catstagramComponent () {
      var directive =  {
        scope: {
          data:'=data'
         },
         controller:catController,
         link: createComponent
      }

      return directive;

      function createComponent () {}
      function catController (){...}
}

@Component({
  selector: 'catstagram',
  templateUrl:'posts.html',
})

export Class Catstagram {
 @Input() data;
 constructor(){
  this.posts = this.data;
 }
}

A Component

  • knows how to interact with its host element.
  • knows how to render itself, so it knows its view.
  • configures dependency injection.
  • has a well-defined public API of property and event bindings.

Component annotation properties

  • selector: string
  • providers?: any[]
  • templateUrl?: string
  • styleUrls?
  • directives?: Array
  • pipes?: Array

Documentation

When a component is instantiated, Angular creates a change detector, which is responsible for propagating the component's bindings. The changeDetection property defines, whether the change detection will be checked every time or only when the component tells it to do so. host - Specifiy the events, actions, properties and attributes related to the host element. lifecycle - Specifies which lifecycle should be notified to the directive. compileChildren - If set to false the compiler does not compile the children of this directive. hostInjector - Defines the set of injectable objects that are visible to a Directive and its light dom children. The changeDetection property defines, whether the change detection will be checked every time or only when the component tells it to do so. ViewInjector defines the set of injectable objects that are visible to its view dom children.

Let's create a component

ng new forward5-app --style=sass

Step - 0

Github

Step - 1

ng g component lists

Step - 2

Adding 3rd party libraries

Life Cycle Hooks

ngOnChanges, when input changes init, do the initial work here doCheck, after init, when every change detection runs viewInit, component and child components are initialized

@Input

<child [data]="dataValue"></child>
@Component({
    selector:'child',
    template:'<p>{{data}}</p>'
})
export class ChildComponent {
    @Input() data;
}
if data is primitive, when it changes the change detection runs if it is a reference, array, object,..., it will only run when the reference changes

@Output

<child [data]="dataValue"></child>
@Component({
    selector:'parent',
    template:'<child (childevent)="onChildEvent($event)"></child>'
})
export class ParentComponent {
    onChildEvent(ev){
        console.log('Event fired',ev);
    }
}
if data is primitive, when it changes the change detection runs if it is a reference, array, object,..., it will only run when the reference changes

@Input

<child [data]="dataValue"></child>
@Component({
    selector:'child',
    template:'<button (click)="onClick()"></button>'
})
export class ChildComponent {
    @Output childEvent = new EventEmitter();

    onClick(){
        this.childEvent.emit('hello!');
    }
}

Services

import { Injectable } from '@angular/core';

@Injectable()
export class DataService(){
 return [...];
}
Services are just another class. We use appInjector in our component annotation to include it. No need for annotations for the service class.

Dependency Injections

import {ListsService} from '../listService.ts'
  @Component({
    providers: [ListService]
})
class PostList {
 constructor(private _listService: ListService) {
  this.posts = [];
 }
 getPosts(){
  this._listService.getList();
  }
}

Angular 2 provides a single API for injecting dependencies. All of them injected into the component’s constructor.

Routing

import { provideRouter, RouterConfig }  from '@angular/router';
import { HomeComponent, ListComponent } fromt './..';

Most routing applications should add a '' element just after the '' tag to tell the router how to compose navigation URLs. Bootstrap takes a list of injector bindings as second argument. Passing routerInjectables here basically makes all the bindings application-wide available.

Route config

import { provideRouter, RouterConfig }  from '@angular/router';
const routes: RouterConfig = [
  {
    path: '',
    redirectTo: '/home',
    pathMatch: 'full'
  },
  {
    path: 'home',
    component: HomeComponent
},
{
  path: 'about',
  component: AboutComponent
}
];

export const appRouterProviders = [
  provideRouter(routes)
];

import { ROUTER_DIRECTIVES } from '@angular/router';
<nav>
 <a [routerlink]="['/home']">Home</a>
 <a [routerlink]="['/about']">About</a>
</nav>
<router-outlet></router-outlet>

Plunker

next to routerInjectables, there are other components that the router module exports. One of them is the RouteConfig class which can be used to decorate a component with routing capabilities.

export class DonePipe  {
 transform(v, args) { return v && v.filter((item) => item.status === 'Done'); }
}

What you should do in Angular 1.x

// Instead of doing this:
app.controller('CatController', function ($scope) {
 $scope.cats = [];
});

// Do this:
app.controller('CatController', function () {
 this.cats = [];
});

Use this instead of scope and use controller as syntax. Using controller as makes it obvious which controller you are accessing in the template when multiple controllers apply to an element. If you are writing your controllers as classes you have easier access to the properties and methods, which will appear on the scope, from inside the controller code. Since there is always a . in the bindings, you don't have to worry about prototypal inheritance masking primitives.

What happenned to scope?

"scope" is bound to the components

Having different kinds of scope, like isolated and non-isolated, can easily lead to issues. So it's best to keep things simple and focus on isolated scopes.

HTTP

import {Http, HTTP_PROVIDERS} from 'angular2/http';
  @Component({
    selector: 'http-app',
    viewProviders: [HTTP_PROVIDERS],
    templateUrl: 'people.html'
})
export class PeopleComponent {
  constructor(http: Http) {
  http.get('people.json')
  // Call map on the response observable to get the parsed people object
  .map(res => res.json())
  // Subscribe to the observable to get the parsed people object and attach it to the
  // component
  .subscribe(people => this.people = people);
  }
}

Http is available as an injectable class, with methods to perform http requests. Calling request returns an Observable which will emit a single Response when a response is received. https://angular.io/docs/js/latest/api/http/Http-class.html

Pipes

Slides

Additional resources

THE END

- Slides - @AysegulYonet

Introduction to Angular 2.0 Created by Aysegul Yonet / twitter@AysegulYonet Slides: bit.ly/ForwardJS-Angular2