Angular Advanced

Educacion IT

Ing. Andres Otaduy

https://github.com/aotaduy

profile

Structure of a project

  • Feature based hirearchy vs component type hierarchy
  • File names: name.type.js ie: movies.controller.js
  • Unit tests name.type.spec.js movies.controller.spec.js

Use a build process

  • Separate dev from deploy
  • Minify and optimize html, css, js
  • Lint and check format
  • Automate tasks (deploy, test,)
  • Use githooks

Separate dev from deploy

dev

app/
    app.module.js
    app.config.js
    components/
        calendar.directive.js
        user-profile.directive.js
        user-profile.directive.html
    layout/
        shell.html
        shell.controller.js
        shell.controller.spec.js
        topnav.controller.js
        topnav.controller.spec.js
    sessions/
        sessions.html
        sessions.controller.js
        sessions.routes.js
        session-detail.html
        session-detail.controller.js
    
people/
    attendees.html
    attendees.controller.js
    people.routes.js
    speakers.html
    speakers.controller.js
    speaker-detail.html
    speaker-detail.controller.js
    people.e2e.spec.js
services/
    data.service.js
    localstorage.service.js
    logger.service.js
    spinner.service.js

Separate dev from deploy

deploy

dist/
    index.html
    js/
        app094hgt.min.js
        vendor065htui.min.js
    css/
        app465rte.min.css
        vendor543rte.min.css
    

Minify and Optimize

  • uglify
  • babel
  • postcss autoprefixer
  • postcss usedcss
  • huge etc...

Linting

  • eslint, jshint, jscs
  • csslint
  • htmlhint

configure the rules for each project, add githooks if the team is large

Exercise

Create a set of gulp tasks that creates in the folder dist/

  • a) a folder named `dist/templates` with all template html files and structure, from the src dir in angular-example.
  • b) a concatenated js file "app.dev.js" for all js files excluding tests, from the src dir in the same repo use gulp-concat
  • c) a minified js file with the contents of all files excluding tests from the src dir in the same repo use gulp-uglify

Style Guides

  • Uniformity
  • Source of truth for code review
  • Improve readability

John Papa - Tod motto - Google

Important Patterns

Exercise

Refactor the app https://github.com/aotaduy/angular-phonecat-14.git into a main module and a set of submodules by feature using the main patterns present on the style guide.

Protractor end to end testing for angularJs
  • Runs in a real browser
  • Use native events as a Real user
  • Automatic Waiting
  • BDD Style by default

Exercise

Using https://github.com/aotaduy/angular-phonecat-14
  • Create More Expectations for the list, empty case, different phone filter, tablet filter
  • Create a test wich clicks the last link for a dell phone and clicks the link using a binding locator
  • Create a test wich clicks all of the links for tablets

UI Router

  • Nested states and Views
  • Multiple Named Views
  • Resolves
  • Custom Data
  • Better URL parameter support
  • URL Rules service

Components

$stateProvider / $state ui-sref ui-view $urlRouter / $urlRouterProvider

Basics

template, templateUrl resolve custom-data, $stateParams
                        
  $stateProvider
    .state('movie-page', {
        title: 'Movie',
        url: '/movie/:movieId',
        templateUrl: 'app/pages/movie/movie-page.html',
        controller: 'MoviePageController',
        controllerAs: 'movieVm',
        resolve: {
            movie: ['$stateParams', 'moviesConnector',
                function($stateParams, moviesConnector) {
                  var id = $stateParams.movieId;
                    return moviesConnector.movieInfo(id)
                      .then(function (response) {
                          return response.data;
                      });
                    }]
                  }
                });
              
                      

Basic API

  • $stateProvider.state(name, {})
  • $state.go(state, params, {location: true, reload:false, notify:true})
  • $state.get
  • urlRouterProvider
  • ui-view ui-sref

Example

Add a NowPlaying page.

Add a page that shows the movies being played from /api/movies/playing using a resolve to get the data. component moviesConnector has the method that retrives this data.

Events

  • $stateChangeStart
  • $stateChangeEnd
  • $stateChangeError
  • event, fromState, toState, paramters
  • event.preventDefault() to stop transition

Techniques

Scope CSS

Adding a class corresponding to the state name you can scope css

Handle Errors

  • Set up an event handler early in your project.
  • Create a redirectTo mechanism

Techniques

Handle Auth and login

Intercept events to protect pages from access.

                        
$rootScope.$on('$stateChangeStart', function(e, to) {
  if (!to.forceLogin) return;
  if (!isLoggedIn($currentUser)) {
    e.preventDefault();
    // Optionally set option.notify to false if you don't want
    // to retrigger another $stateChangeStart event
    $state.go(result.to, result.params, {notify: false});
  }
                        
                      

Excercise

Add an ordering option (asc, desc) and ordering field (title, release_date, vote_average) to simple search view in angular-example. to control the order of the query. This parameter shold persist in the query string to allow deeplinking of search results.

Exercise

Create a search page with an input for the search string, and controls for the order of results (asc, desc) of the results. Search string should be part of the url and ordering should be a queryString parameter. A user should be able to get the same resluts by copy and pasting the link in another browser.

Create a new page for the login route. Add the events to protect the movies details page with the login. Save the login state in a mock service that logs in any user with a number on his password.

Url handling

  • urlRouterProvider
  • when(what, String|Function) ret: false|String|true
  • otherwise(String|Function)
  • rule(Function) ret: Set the url

Nested States

  • Parent Child relationship
  • Inherit resolves and custom data
  • Prefix Url
  • Abstract States
  • Creation via dot notation or parent property
  • Parent must exist
  • Scope inheritance

Abstract States

Multiple Named Views

  • Allows multiple views place holders in a template
  • Uses views property in the state

Exercise

Create a page that shows a movie header with the title and poster, and three tabs. one with the overview of the movie, release date, and basic data. Other with the similar movies, and another with a list of genres. Page should receive the id as a parameter and if the user clicks in a movie poster should navigate to that movie in the same page.

Angular Directives

  • $compile $digest cycle
  • Directive Definition Object Details
  • Components
  • Transclussion
  • Performance
  • Memory Leaks
  • Decorators

$compile cycle

  • Traverse DOM : list of directives
  • sort Directives by priority
  • compile each directive: get link functions
  • Link each directive: set watchers, and register listeners

$digest will run at least once, and

$apply cycle

  • On interaction $apply()
  • Call $digest: run over watch list for dirty
  • Check if dirty restart $digest
  • Repaint browser

At least once, tops 10.

Directives in detail

  • Replacing
  • Linking
  • Compiling
  • Transclussion

Directive Types

  • Element
  • Atribute
  • Class
  • Comment
                    






                  

Replacing directives

                    
.directive('creditCardSelector', function () {
          return {
              restrict: 'E',
              scope: {
                  cards: '=', // Array of cards
                  selectedCard: '=?', // Current selected card
                  removeAction: '&?', // Callback action
                  showRemove: '=?' // Boolean to show delete card button
              },
              templateUrl: 'templates/components/creditCardSelector/creditCardSelector.html',
              controller: 'CreditCardSelectorController',
              controllerAs: 'creditCardSelectorVm',
              bindToController: true
          };
      });
                    
                  

Linking Directives

define a link: function or object.

                    
  var NG_HIDE_CLASS = 'ng-hide';
  var NG_HIDE_IN_PROGRESS_CLASS = 'ng-hide-animate';
  ngShowDirective = ['$animate', function($animate) {
    return {
      restrict: 'A',
      multiElement: true,
      link: function(scope, element, attr) {
        scope.$watch(attr.ngShow, function ngShowWatchAction(value) {
          $animate[value ? 'removeClass' : 'addClass'](element, NG_HIDE_CLASS, {
            tempClasses: NG_HIDE_IN_PROGRESS_CLASS
          });
        });
      }
    };
  }];

                  

Compiling Directives

When need to add new directives to the DOM.

                    
    .directive("directiveName",function () {
      return {
        controller: function() {
          // controller code here...
        },
        compile: {

          // compile code here...

          return {

            pre: function() {
              // pre-link code here...
            },

            post: function() {
              // post-link code here...
            }
          };
        }
      }
    })
                  

Execution Order

image from toptal blog, eric w. greene

Exercise

Create an atribute directive deletable that adds a delete button to the top left of any block element. And triggers an event $deleted when done.

hint: Use the compile function.

Test your directive in an ng-repeat loop.

Transclusion directives

Wrap arbitrary content

    • Single ngTransclude
    • Multislot
  • Transclusion function

Performance digest cycle

  • binding to functions
  • watch entire objects
  • long lists
  • complex filters
  • one time binding {{::}}

Performance DOM manipulation

  • track by in ng repeat
  • ng-if vs ng-show
  • compile vs link
  • preLink vs postLink

Tunning HTTP Connections

  • Promises
  • Caching
  • Headers
  • Transformation
  • Interceptors

Promises

On Object with three states, pending, fullfilled, resolved. Representing a future value.


function(aPromise, $q) {
  aPromise
    .then(successCallback, errorCallback)
    .catch(errorCallback)
    .finally(finallyCallback);
  rejected = $q.reject();
  resolved = $q.when();
  newPromise = $q.all([rejected, resolved, aPromise]);
  newPromise = $q.race([rejected, resolved, aPromise]);
  //Create promises
  deferred = $q.defer();
  deferred.resolve(aValue);
  deferred.reject(aValue);
  return deferred.promise;
}

antipatterns

Anti-patterns


function anAsyncCall() {
    var promise = doSomethingAsync();
    promise.then(function() {
        somethingComplicated();
    });

    return promise;
}

  function anAsyncCall() {
      var promise = doSomethingAsync();
      return promise.then(function() {
          somethingComplicated()
      });
  }
}

Broken Chain

Anti-patterns

somethingAsync.then(
    function() {
        return somethingElseAsync();
    },
    function(err) {
        handleMyError(err);
});
somethingAsync
    .then(function() {
        return somethingElseAsync();
    })
    .catch(function(err) {
        handleMyError(err);
    });

Keen Error Handler

Anti-patterns

var promise;
if (asyncCallNeeded)
  promise = doSomethingAsync();
else
  promise = $q.when(42);

promise.then(function() {
  doSomethingCool();
});
$q.when(asyncCallNeeded ? doSomethingAsync() : 42)
  .then(function(value){
    doSomethingGood();
  })
  .catch(function(err) {
    handleTheError();
  });
}

The Ghost Promise

Anti-patterns

var deferred = $q.defer();
doSomethingAsync().then(function(res) {
    res = manipulateMeInSomeWay(res);
    deferred.resolve(res);
}, function(err) {
    deferred.reject(err);
});

return deferred.promise;
return doSomethingAsync().then(function(res) {
    return manipulateMeInSomeWay(res);
});

The Forgotten Promise

$http

var config = {
  method: 'GET',
  url: '/someUrl',
  params: {query: 'star wars'},
  data: {user: 'pepe'}
  headers: {'Application-Id': 'fdkjshagfkjhasdgfqwybbsdf'},
  timeout: 1000, // response status will be -1
  cache: true,
  transformRequest: transformFun,
  transformResponse: transformFun2
};

$http(config).then(function successCallback(response) {
    // this callback will be called asynchronously
    // when the response is available
  }, function errorCallback(response) {
    // called asynchronously if an error occurs
    // or server returns response with an error status.
  });

$httpProvider

set default values for $http

                  $httpProvider.defaults.headers.common = {
    'X-App': 'AngularJs'
    };
$httpProvider.defaults.headers.put = {...};
$httpProvider.defaults.cache = true;
$httpProvider.useApplyAsync(true);

Cache

  • Caching everything,
  • Cache some responses
  • Use different caches
$http.defaults.cache = true;
$http.get('/api/random/slow', {cache: true});

Cache

  • cache all requests GET and JSONP
  • cache-control: not used
  • key is the querystring plus params

Setting headers

  • At config: $httpProvider.defaults.headers
  • At runtime: $http.defaults.headers
  • At request: $http({headers: {} })
  • Remove a default header: set to undefined

Exercise

Modify randomService so when it receives a 401 on the getRandom request (endpoint: /api/random/fast) so it Authorizes via HTTP Basic Authentication user: test password: 123456

Example
                      Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
                      QWxhZGRpbjpPcGVuU2VzYW1l is base64 encoding of user:passwd 
                    

Transformations

Pipeline to transform requests and responses data and headers

Transformations defaults

  • request if data is object serialize it into JSON format.
  • response: If XSRF prefix is detected, strip it.
  • response: If JSON response is detected, deserialize it using a JSON parser.

Interceptors

pre processing of requests and responses, injectable services and factories. Free to modify the response or request config. could return a promise

                    module.factory('interceptor', function($log) {
    $log.debug('$log is here to show you that this is a regular factory with injection');

    var myInterceptor = {
      response: function () {},
      responseError: function () {},
        ....
        ....
    };
    return myInterceptor;
});
                    
                  

Interceptor

  • request
  • requestError
  • response
  • responseError
                    module.config(['$httpProvider', function($httpProvider) {
      $httpProvider.interceptors.push('myInterceptor');
  }]);
                  

Interceptors

  • Session
  • Session recovery
  • Embeding status code - REST enveloping Enveloping
  • General error handling
  • General timeout handling

Angular Unit Testing

Angular is a framework conceived with testeability in mind.

  • Separation of Concerns
  • Dependency injection
  • angular-mocks
  • karma

Mocha Chai Sinon

describe('Array', function() {
  describe('#indexOf()', function() {
    it('should return -1 when the value is not present', function() {
      assert.equal(-1, [1,2,3].indexOf(4));
    });
  });
});
                

Chai

//BDD Should
foo.should.be.a('string');
foo.should.equal('bar');
foo.should.have.length(3);
tea.should.have.property('flavors')
  .with.length(3);
//BDD Expect expect(foo).to.be.a('string');
expect(foo).to.equal('bar');
expect(foo).to.have.length(3);
expect(tea).to.have.property('flavors')
.with.length(3);
//Assert
assert.typeOf(foo, 'string');
assert.equal(foo, 'bar');
assert.lengthOf(foo, 3)
assert.property(tea, 'flavors');
assert.lengthOf(tea.flavors, 3);

Chai Chains

Only for readibility: to be been is that which and has have with at of same

//BDD Expect expect(foo).to.be.a('string');
expect(foo).to.be.equal.to('bar');
expect(foo).to.have.same.length(3);
expect(tea).to.have.a.property('flavors').with.length(3);
expect(tea).to.have.been.equal('flavors').and.have.a.length.of(3);

angular-mocks

ngMock, A module that facilitates mocking in unit tests.

  • angular.mock.module
  • angular.mock.inject
  • $httpBackend
  • tzDate
  • Mock implementation of common services ($log, $interval, $timeout, $controller, etc)

module()

  • called with a string
  • called with a function($provide){}
  • called with {value: value}
describe('MyApp', function() {

  // You need to load modules that you want to test,
  // it loads only the "ng" module by default.
  beforeEach(module('myApplicationModule'));
  beforeEach(module(function($provide) {
        $provide.value('version', 'overridden'); // override version here
      });
    }));

inject()

injects components into the function parameter

var myService;

// Wrap the parameter in underscores
beforeEach( inject( function(_myService_){
  myService = _myService_;
}));

// Use myService in a series of tests.
it('makes use of myService', function() {
  myService.doStuff();
});

Testing a $filter

Easiest case since filters should not have side effects

describe('Filter: words', function() {
    'use strict';
   var filter;
   // Refresh the $filter every time.
   beforeEach(module('common-filters')); // load the module
   beforeEach(inject(function(_$filter_) { // inject filter
       filter = _$filter_;
        })
    );

   it('should return array of words', function() {
     expect(filter('words')('one two three')).to.deep.equal(['one', 'two', 'three']);
   });
   it('should return empty array', function() {
     expect(filter('words')('')).to.equal('');
   });
});

Testing a $controller

describe('Controller: todo-list', function() {
    'use strict';
   var controller,
        scope;
   // Refresh the $filter every time.
   beforeEach(module('todoListControllerDemo'));
   beforeEach(inject(function(_$rootScope_, $controller) {
       scope = _$rootScope_.$new();
       controller = $controller('TodoListController',
        {$scope: scope});
        })
    );

    it('should start empty', function() {
        expect(controller.list.length).to.equal(0);
    });

   it('should add items', function() {
       controller.itemText = 'First item';
       controller.add(controller.itemText);
       expect(controller.list.length).to.equal(1);
       controller.itemText = 'Second item';
       controller.add(controller.itemText);
       expect(controller.list.length).to.equal(2);
       controller.remove(1);
       expect(controller.list.length).to.equal(1);
   });

});

karma logo

Test runner

  • generate index.html with files
  • Run browser(s) in background
  • Run tests
  • Collect and present results

Available for Chrome and Chrome Canary, Firefox, Safari, PhantomJS, Opera, IE, etc

Karma

npm install -g karma-cli
npm install karma-mocha karma-chrome-launcher karma-phantomjs-launcher --save-dev

Create karma.conf.js in the root of your project

// karma.conf.js
module.exports = function(config) {
  config.set({
    frameworks: ['mocha'],

    files: [
      '*.js'
    ],

    client: {
      mocha: {
        // change Karma's debug.html to the mocha web reporter
        reporter: 'html',

        // require specific files after Mocha is initialized
        require: [require.resolve('bdd-lazy-var/bdd_lazy_var_global')],

        // custom ui, defined in required file above
        ui: 'bdd-lazy-var/global',
      }
    }
  });
};

Code Coverage with istanbul

npm install karma karma-coverage --save-dev
// karma.conf.js
module.exports = function(config) {
  config.set({
    files: [
      'src/**/*.js',
      'test/**/*.js'
    ],

    // coverage reporter generates the coverage
    reporters: ['progress', 'coverage'],

    preprocessors: {
      // source files, that you wanna generate coverage for
      // do not include tests or libraries
      // (these files will be instrumented by Istanbul)
      'src/**/*.js': ['coverage']
    },

    // optionally, configure the reporter
    coverageReporter: {
      type : 'html',
      dir : 'coverage/'
    }
  });
};

Mocha + Chai vs Jasmine

  • Jasmine has almost everything builtin
  • Mocha uses a modular approach
  • describe, it, async API is the same
  • Variation in some precondition methods
  • Variation in some assertions, and expectations
  • Mocha includes a test runner, Jasmine not

Exercise

  1. Create a test for the titleCase filter
  2. Create a test for the search-page.controller.js

Both in angular-example repo.

$httpBackend

  • Create definitions with whenGET().respond()
  • Create expectations with expectGET().respond()
  • flush() to remove async responses an ease test writing
  • Verifiy postconditions, verifyNoOutstandingRequest, verifyNoOutstandingExpectation
  • Match regular expressions for routes and querystring parameters

Testing a service with http communication

describe('Service: moviesConnector', function() {
    'use strict';
   var
        service,
       $httpBackend;

   // Refresh the $filter every time.
   beforeEach(module('movies.connector'));
   beforeEach(inject(function (_$httpBackend_, _moviesConnector_) {
     $httpBackend = _$httpBackend_;
     $httpBackend.whenGET('/api/movies/search/star')
       .respond({
           results:[
               {title: 'Star Wars'},
               {title: 'Star Trek'}
           ]
       });
       $httpBackend.whenGET('/api/movies/playing/')
         .respond({
             results:[
                 {title: 'Deadpool'},
                 {title: 'Superman'}
             ]
         });
         $httpBackend.whenGET('/api/movies/')
           .respond({
               results:[
                   {title: 'Deadpool'},
                   {title: 'Superman'}
               ]
           });
       service = _moviesConnector_;
   }));

   afterEach(function () {
     $httpBackend.verifyNoOutstandingRequest();
   });

    it('should get nowPlaying movies', function() {
        service.nowPlaying().then(function (result) {
          expect(result.data.results[0].title).to.equal('Deadpool');
        });
        $httpBackend.flush();
    });


    it('should get nowPlaying movies', function() {
      $httpBackend.expectGET('/api/movies/playing/')
        .respond({
            results:[
                {title: 'Deadpool'}
            ]
        });
        service.nowPlaying();
        $httpBackend.flush();
    });

    it('should get nowPlaying movies and top rated', function() {
      $httpBackend.expectGET('/api/movies/playing/')
        .respond({
            results:[
                {title: 'Deadpool'}
            ]
        });
        service.nowPlaying();
        service.topRatedMovies();
        $httpBackend.flush();
    });
    it('should search', function() {
      $httpBackend.expect('GET', /api\/movies\/search\/.*/ )
        .respond({
            results:[
                {title: 'Deadpool'},
                {title: 'Star wars'},
                {title: 'Star trek'}
            ]
        });
        service.search('peperino');
        $httpBackend.flush();
    });
});

Testing async code

it('should getRandomNumberDeferred', function(done) {
      service.getRandomDeferred().then(function (response) {
        expect(response).to.equal((0.7).toFixed(3));
        done();
      });
      service.getRandomDeferred().then(function (response) {
        done();
      });
      $timeout.flush();
      $rootScope.$digest();
});

Testing directives

describe('ngClick', function() {
  var element;

  afterEach(function() {
    dealoc(element);
  });

  it('should get called on a click', inject(function($rootScope, $compile) {
    element = $compile('
')($rootScope); $rootScope.$digest(); expect($rootScope.clicked).toBeFalsy(); browserTrigger(element, 'click'); expect($rootScope.clicked).toEqual(true); })); it('should pass event object', inject(function($rootScope, $compile) { element = $compile('
')($rootScope); $rootScope.$digest(); browserTrigger(element, 'click'); expect($rootScope.event).toBeDefined(); })); });

Sinon

  • spies
  • stubs
  • mocks

Sinon spies

A spy is a function that records the calls and return values.

"test should call subscribers on publish": function () {
    var callback = sinon.spy();
    PubSub.subscribe("message", callback);

    PubSub.publishSync("message");

    assertTrue(callback.called);
}

Sinon stubs

a stub is a function with pre trained behaviour

    it('should getRandom', function() {
        sinon.stub(service, 'getRandom');
        service.getRandom.onCall(0).returns($q.when(0.2));
        service.getRandom.onCall(1).returns($q.when(0.3));
        controller.updateNextRandom();
        $rootScope.$digest();
        expect(controller.messageHistory).to.equal('0.2\n');
        controller.updateNextRandom();
        $rootScope.$digest();
        expect(controller.messageHistory).to.equal('0.2\n0.3\n');
        service.getRandom.restore();
    });

Sinon Mocks

Mocks are stubs or spies that you can verify on

    it('should getRandomLocal mock', function() {
        var mock = sinon.mock(Math);
        mock.expects('random').once();
        controller.updateNextRandomLocal();
        $rootScope.$digest();
        mock.verify();
        mock.restore();
    });

Exercise

Create a unit test for the search directive (client/app/components/search-box) in angular-example

Angular 2 Intro

  • TypeScript -> ES6
  • Modules Ng and ES6
  • Components
  • Templates
  • Bootstraping
  • Data Binding
  • Directives
  • Services
  • HTTP

Angular 2 Pros

  • TypeScript and ES6
  • Component Architecture
  • Modularity (ES6)
  • RxJs
  • Performance
  • Simpler Architecture
  • Modular CSS

Angular 2 Cons

  • Not Yet Released (2016/09)
  • TypeScript
  • Lot of Changes
  • Complexity
  • Template Syntax
  • Removed $scope
  • Better change detection

Architecture

Components

  • Defined as annotated class
  • Instance bound to template
  • Explicit dependencies
  • stylesheets
  • Uses Shadow Dom
  • Lyfecycle hooks

Component Syntax

import { Component } from '@angular/core';
import {TodoItem} from './todo-item'

@Component({
  selector: 'app-root',
  templateUrl: 'app.component.html',
  styleUrls: ['app.component.css']
})
export class AppComponent {
  title = 'app works!';
  list: Array<TodoItem>;
  item: TodoItem;

  constructor() {
    this.list = [];
    this.item = new TodoItem;
  }

  add( anItem: TodoItem) {
    this.list.push(anItem);
    this.item = new TodoItem;
  }

  remove( index: number) {
    this.list.splice(index,1);
  }
}

Templates


<h1 [ngClass]="{'big': list.length > 5}" >Todo {{list.length}}</h1>
  <input type="text" [(ngModel)]="item.text">
  <input type="button"  value="Add" (click)="add(item)">
  <ul>
    <li
    [ngClass]="{'done': todoItem.done}" \
    *ngFor="let todoItem of list; let i = index">
      <input type="checkbox" [(ngModel)]="todoItem.done">
        {{todoItem.text}}
        <input
          *ngIf="todoItem.done"
          (click)="remove(i)"
          type="button"
          name="name"
          value="X">
      </li>
  </ul>

Template elements

  • DOM Property and events not HTML Attributes
  • bindings {{}} no side efects: Expression
  • attribute []
  • events (): Statements $event available
  • Two Way [()]
  • Directives
  • Components
  • Pipes

{{Template Expression}}

  • no side effects =, new, ++ ;
  • scope is coponent instance
  • no global namespace
  • Quick
  • Simplicity
  • Idempotent

Template Statements

Attribute, Class and Style bindings

<table border=1>
  <!--  expression calculates colspan=2 -->
  <tr><td [attr.colspan]="1 + 1">One-Two</td></tr>

  <!-- ERROR: There is no `colspan` property to set!
    <tr><td colspan="{{1 + 1}}">Three-Four</td></tr>
  -->

  <tr><td>Five</td><td>Six</td></tr>
</table>

Class Binding

<!-- toggle the "special" class on/off with a property -->
<div [class.special]="isSpecial">The class binding is special</div>

<!-- binding to `class.special` trumps the class attribute -->
<div class="special"
     [class.special]="!isSpecial">This one is not so special</div>

Style Binding

<button [style.color] = "isSpecial ? 'red': 'green'">Red</button>
<button [style.background-color]="canSave ? 'cyan': 'grey'" >Save</button>
<button [style.font-size.em]="isSpecial ? 3 : 1" >Big</button>
<button [style.font-size.%]="!isSpecial ? 150 : 50" >Small</button>

Built in directives

  • Many not needed
  • NgClass, NGStyle
  • *NgId *NgFor *NgSwitch

Template reference Variables


<!-- phone refers to the input element; pass its `value` to an event handler -->
  <input #phone placeholder="phone number">
  <button (click)="callPhone(phone.value)">Call</button>

  <!-- fax refers to the input element; pass its `value` to an event handler -->
  <input ref-fax placeholder="fax number">
  <button (click)="callFax(fax.value)">Fax</button>

Modules and NgModule

export class ZipCodeValidator implements StringValidator {
    isAcceptable(s: string) {
        return s.length === 5 && numberRegexp.test(s);
    }
}
import { ZipCodeValidator } from "./ZipCodeValidator";

let myValidator = new ZipCodeValidator();
 NgModule(options : {
constructor(options?: NgModuleMetadataType)
providers : Provider[]
declarations : Array<Type<any>|any[]>
imports : Array<Type<any>|ModuleWithProviders|any[]>
exports : Array<Type<any>|any[]>
entryComponents : Array<Type<any>|any[]>
bootstrap : Array<Type<any>|any[]>
schemas : Array<SchemaMetadata|any[]>
}) 

Subcomponents Input and Output

import { Component, Input, Output, EventEmitter } from '@angular/core';

import {TodoItem} from './todo-item';

@Component({
    selector: 'todo-line',
    template: `
    <input type="checkbox" [(ngModel)]="item.done">
      {{item.text}}
      <input type="button" name="name" value="X" (click)="remove(item)">
`
})
export class TodoLine {
  @Input() item: TodoItem;
  @Output() removeRequested: EventEmitter = new EventEmitter();

  remove( index: number) {
    this.removeRequested.emit(this.item)
  }
}

Exercise

Create a CRUD app for users, with login, email and password. Create a component for the form and other for the list with the form.

Routing

WIP!

Angular2 Services

  • Dependency Injection
  • Http RxJs
  • Singleton depends on declaration

Bootstraping

import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app.module';

platformBrowserDynamic().bootstrapModule(AppModule);