Angular 1.5 and Beyond

FRP ideas meet real life apps

Angular is changing

Nadav Sinai

JS expert @

Misterbit

 

ns@nadavsinai.com

github.com/nadavsinai

@nadavsinai

The React revolution (~2013)

  • Functional view of UI via composable components
  • Self-contained - HTML (JSX), JS, Css. - Modules.
  • NPM eco system, bundler (Webpack) build, Babel etc.
  • No dirty checking - programatic state/view sync - of the whole system - but actualy DOM rendering is done after virtual DOM diff. - only changed nodes updated - slow DOM side effects postponed to the boundary of the system
  • Encourages one way data flow, very predictable components
  • introduced FLUX State Management - very structured
  • Adopts ES6 standards, & new JS trends like immutability

Angular 2

Angular 2 is a very different framework to angular 1.5

 

  • Components
  • Multi-thredding
  • Offline Compilation (server, pre-render etc)
  • Modular - NPM ecosystem, build via bundler, CLI tools
  • Types (via typescript/dart) in DI
  • Bare template API (no ng-xxxx directives to learn)
  • Immutability and Observable streams in core

What can we do with Angular 1.x Apps?

  • keep things as they are...
  • Implement new thinking with old tools
  • migrate to new thinking and new tools - step by step
  • build completely new apps

Start with the tools...

Angular 1.5 - new features

 .component method

Helper to write code by convention and best practices

  • cuts down on boilerplate
  • provides sane defaults
  • allows stateless components
  • looks more like angular 2

Angular 1.4

.directive('counter', function counter() {
  return {
    scope: {},
    bindToController: {
      count: '='
    },
    controller: function () {
      function increment() {
        this.count++;
      }
      function decrement() {
        this.count--;
      }
      this.increment = increment;
      this.decrement = decrement;
    },
    controllerAs: 'counter',
    template: [
      '<div class="todo">',
        '<input type="text" ng-model="counter.count">',
        '<button type="button" ng-click="counter.decrement();">-</button>',
        '<button type="button" ng-click="counter.increment();">+</button>',
      '</div>'
    ].join('')
  };
});

Angular 1.5

.component('counter', {
  template: [
      '<div class="todo">',
        '<input type="text" ng-model="$ctrl.count">',
        '<button type="button" ng-click="$ctrl.decrement();">-</button>',
        '<button type="button" ng-click="$ctrl.increment();">+</button>',
      '</div>'
    ].join(''),
  bindings: {
    count: '='
  },
  controller: class Counter {
     constructor(//DI here//){
        this.count = 0;
    }
   
     increment() {
      this.count++;
    }

    decrement() {
      this.count--;
    }
  }
});

Template as a function

.component('counter', {
  template: ($element,$attr) {
 //do your logic 
    return [
      '<div class="todo">',
        '<input type="text" ng-model="$ctrl.count">',
        '<button type="button" ng-click="$ctrl.decrement();">-</button>',
        '<button type="button" ng-click="$ctrl.increment();">+</button>',
      '</div>'
       ].join('');
},
  bindings: {
    count: '='
  },
  controller: class Counter {
     constructor(){
        this.count = 0;
    }
   
     increment() {
      this.count++;
    }

    decrement() {
      this.count--;
    }
  }
});

Angular 2.0

@Component({
  template:  [
      '<div class="todo">',
        '<input type="text" #count='' ngControl='count' [(ngModel)]="count">',
        '<button type="button" (click)="decrement();">-</button>',
        '<button type="button" (click)="increment();">+</button>',
      '</div>'
       ].join('');
}) 
class Counter {
    @Input()
    public count:number=0;
   
    increment() {
      this.count++;
    }

    decrement() {
      this.count--;
    }
  }
});

In Angular 2 -

Projection is the new transclution

In Angular 1.5

  • components have transclusion enabled by default
  • it possible to use multi-transclusion slots

Transclusion as a source of data 

//html
<multislot-card>
  <card-title>Leonard Cohen</card-title>
  <card-song>Like a Bird on a wire</card-song>
</multislot-card>
//


//js
angular
  .module('app')
  .directive('multislotCard', multipleTransclusion);
  
function multipleTransclusion(){
  return {
    restrict: 'E',
    transclude: {
      'title': '?cardTitle',
      'song': '?cardSong',
    },
    template:
      '<div>' + 
        '<h3 ng-transclude="title">No title</h3>' + 
        '<i ng-transclude="song">Empty</i>' +
      '</div>'
  };
}

Require and $onInit

Require is used to get other controllers instantiated on same or parent nodes

 

Angular 1.4

angular
  .module('app', [])
  .directive('parentComponent', function () {
    scope: {},
    require: ['^parentDirective', 'ngModel'],
    controller: function () {
      // controller logic
    }
    link: function ($scope, $element, $attrs, $ctrl) {
      // $ctrl[0] === ^parentDirective
      // $ctrl[1] === ^ngModel
    },
    template: `
      <div>
        Component: {{ $ctrl.state }}
      </div>
    `
  });

Require and $onInit

We can now use an object

angular
  .module('app', [])
  .directive('parentComponent', function () {
    scope: {},
    require: {
              ngModelCtrl: 'ngModel'
    },
    controller: function () {
            this.ngModelCtrl.... /// created for us
    }
    template: `
      <div>
        Component: {{ $ctrl.state }}
      </div>
    `
  });

Require and $onInit

angular
  .module('app', [])
  .directive('parentComponent', function () {
    scope: {},
    require: {
              ngModelCtrl: 'ngModel'
    },
    controller: function () {
            this.ngModelCtrl.formatter.push(...) // runtime error!
    }
    template: `
      <div>
        Component: {{ $ctrl.state }}
      </div>
    `
  });

Require and $onInit

But parents are instantiated after children,
and order & priority is not known to controller

angular
  .module('app', [])
  .directive('parentComponent', function () {
    scope: {},
    require: {
              ngModelCtrl: 'ngModel'
    },
    controller: class ParentComponent() {
        $onInit(){
            this.ngModelCtrl.formatter.push(...)// do your thing
    }
  });

One way Data binding

angular
  .module('app', [])
  .component('example', {
  bindings: {
    obj: '<',
    prim: '<'
  },
  template: `
    <div class="section">
      <h4>
        Isolate Component
      </h4>
      <p>Object: {{ $ctrl.obj }}</p>
      <p>Primitive: {{ $ctrl.prim }}</p>
      <a href="" ng-click="$ctrl.updateValues();">
        Change Isolate Values
      </a>
    </div>
  `,
  controller: function () {
    this.updateValues = function () {
      this.prim = 10;
      this.obj = {
        john: {
          age: 35,
          location: 'Unknown'
        }
      };
    };
  }
};
)})
 
angular
  .module('app')  
  .controller('ParentController', function ParentController() {
  this.somePrimitive = 99;
  this.someObject = {
    todd: {
      age: 25,
      location: 'England, UK'
    }
  };
  this.updateValues = function () {
    this.somePrimitive = 33;
    this.someObject = {
      jilles: {
        age: 20,
        location: 'Netherlands'
      }
    };
  };
}
);

Stateless component

// usage: <name-component></name-component>
angular.module('app).component('nameComponent',{
  bindings: {
    name: '=',
    age: '='
  },
  template: `
    <div>,
      <p>Name: {{$ctrl.name}}</p>
      <p>Age: {{$ctrl.age}}</p>
    </div>`
});

Types of Components

Presetational -  components

(dumb component)


Types of Components

Business -
Stateful
(smart components)

 

Types of Components

View Routing -
(average guy which by chance has the control)

 

Presentation components

 

  • Display the user interface

  • Stateless (dumb / pure)

  • Data arrives via bindings (inputs)

  • Data leaves via events (outputs)

 

 

 

Business Components

 

  • Access services & state

  • Stateful (smart / impure / container)

  • Do not provide interactive user interface

  • Render other Components

View Components

 

  • Build the current view (from a URL)

  • Specialist (smart/router) components

  • Create components dynamically (via a Router)

  • Can be entry points to the application

More on LifeCycle hooks

 

  • allow us to work without $scope
  • provide a similar environment to angular 2

 

  • $onInit()
  • $onDestroy()
  • $postLink()
  • $onChanges(changes)

$onChanges

 

 

 

myVillain.prototype = {
  $onChanges: function(changes) {
    if (changes.villain) {
      this.fullName = getFullName(this.villain);
    }
  },
  ...
};

Testing Components

More new stuff with 1.5

  • Lazy transclusion
  • ng-animate-swap
  • $resolve in ng-route
  • cancelable requests in ng-resource

Ng-animate-swap

Angular 2 preperation

  • Implement new features as components
  • move to Typescript
  • use NG-FORWARD/NG-UPGRADE
  • learn to use Observable streams
  • Learn FRP thinking
  • learn to use Immutability
  • A superset of ES6, compiles to ES 6/5/3
  • Allows optional & gradual typing
  • Interfaces,Enums, Privacy ....
  • Developer oriented - no runtime 
  • Allows Amazing IDE environment & tools
  • Type Metadata used by Angular 2.0 DI
  • A Helper module to make the syntax even closer to angular Does not require angular 2
  • works with decorators (typescript or babel)

 

  • Use ng 1.x and ng2 togethers
  • Injectors are bridged 
  • Components and directives can be mixed

Observables 

 

 

 

Push vs Pull

 

Observables are lazy Push collections of multiple values. They fill the missing spot in the following table:

Single Multiple
Pull Function Iterator/generator
Push Promise/callback ???

Push vs Pull

 

Observables are lazy Push collections of multiple values. They fill the missing spot in the following table:

Single Multiple
Pull Function Iterator/generator
Push Promise/callback Observable

 Data streams

Observable spec.

 

 

 

var observable = Rx.Observable.create(function (observer) {
  observer.onNext(42);
  observer.onCompleted();
  return function(){
    //do your cleanup stuff here
    }
});


var subscription = observable.subscribe(
  function (value) {
      console.log('Next: %s.', value);
  },
  function (ev) {
      console.log('Error: %s!', ev);
  },
  function () {
      console.log('Completed!');
  }
);

subscription.dispose();

RxJs - Reactive Extensions to Javascript

Reactive data streams

Angular and Observables (rx-angular)

 

 

 

observeOnScope($scope, 'search')
  .throttle(1000)
  .map(function(change){
    return change.newValue || "";
  })
  .distinctUntilChanged()
  .flatMapLatest(searchWikipedia)
  .safeApply($scope, function(result) {
    $scope.data = result;
  })
  .subscribe();

Angular and Observables (rx-angular)

 

 

 

function searchWikipedia(term) {
  return rx.Observable.fromPromise($http({
      url: "http://en.wikipedia.org/w/api.php?&callback=JSON_CALLBACK",
      method: "jsonp",
      params: {
        action: "opensearch",
        search: encodeURI(term),
        format: "json"
      }
    })
  );             
}

Programming going FRP

// OOP

let c = new Calculator(0);
// one needs to learn API.
c.add(3);
/// keeping internal state
let result = c.value();
c.subtract(2);
let nextResult = let result = c.value();

///FRP (Functional only really here)

function add(a,b){
    return a+ b;
}

let result = add(0,3);
let nextResult  = add(result,-2);

//is also polymorphic

let floaty = add(5.56,15.7);

// and even 
let together = add('this',' and that');


///Function Rules (laws)

// associative

 add(add(1, 2), 4) == add(1, add(2, 4)) 

// commutative 

add(4, 1) == add(1, 4)

 // identity 

add(n, 0) == n 

// distributive 

multiply(2, add(3,4)) == add(multiply(2, 3), multiply(2, 4))


///Functional Rules (laws)

//composable

var reverseCap = compose(capitalize, reverse);
reverseCap(“hello”) //=> “Olleh” Composition

//associativity also works in functions

compose(compose(f, g), h) == compose(f, compose(g, h)) Composition (associativity)

/// also for larger scale
var getFromDb = compose(pluck('rows'), User.findAll);
var cleanUpData = compose(capitalize, pluck('name'));
var renderTemplate = TemplateEngine.render(‘users_table');
var makePage = compose(renderTemplate, map(cleanUpData), getFromDb);

 makePage({limit: 20}) makePage({limit: 20})

Function Purity

A function is pure when:

  • it always gives the same o/p for one i/p
  • it has no side effects
  • never relies on external state

Pure Functions

 

Purity makes function

 

  1. Reliable

  2. Testable

  3. Portable

  4. Memoizable

  5. Parallelizable

Immutability 

 

 

 

Mutability creates probelms

function impureAdd1(val){
    myArray.push(val);
}

function impureAdd2(val,myArray){
    myArray.push(val);
}


function pureAdd(val,myArray){
    return [...myArray,val];
}

JS Data

Mutable Immutable
Object String
Array Number
Map/Set Boolean

We can keep native

function immutableSplice(arr, start, deleteCount, ...items) {
  return [ ...arr.slice(0, start), ...items, ...arr.slice(start + deleteCount) ]
}
function immutableReverse(arr) {
  return [ ...arr ].reverse()
}

Facebook Lib (2014)

  • Immutable Collections
  • Angular integration
  • Deep optimizations (TRIE Hashing)
  • Great docs

Links

Thanks

Angular 1.5 &Beyond

By Nadav SInai

Angular 1.5 &Beyond

Review of how to keep AngularJS app up to date in 2016

  • 1,871