AUTHOR

TOMAS HERICH

Software Engineer Frontend / Java @mimacom

 

         medium.com/@tomastrajan

         @tomastrajan

         github.com/tomastrajan

 

  • more than two years of Angular JS experience
  • projects of various sizes and in various domains
  • Angular JS focused blog
  • curious affinity for frontend build systems :)
  • reddit /r/javascript /r/programming /r/angularjs addict

LET'S GET STARTED

OVERVIEw

Component Pattern

  • component pattern, using directive
  • using ui-router to navigate to particular component
  • reusing components
  • components with newNgRouter
  • looking forward - how to migrate to Angular 2.0

 

  • evolution of Angular JS concepts ($scope, ngRoute, ...) 

 

ROAD TO COMPONENTS

LIVE DEMO

  • if we have enough time :)

 

EVOLUTION OF CONCEPTS

ROAD FROM FROM ANCIENT ng-CONTROLLER to COMPONENTS

EVOLUTION OF CONCEPTS

ROAD FROM FROM ANCIENT ng-CONTROLLER to COMPONENTS

  • ng-controller directly in HTML template
  • $scope (& scope inheritance and resulting problems)
  • ngRoute - better but not flexible (eg: no nested states / views)
  • controllerAs syntax - solution to $scope problems (Angular 1.2+)
  • ui-router - solution to ngRoute problems (nested states, partial views)
  • bindToController syntax for implementing directives without $scope

 

  • component pattern for Angular 1.X
  • ngNewRouter - easier routing to components (convention over conf...)
  • Angular 2.0 first class components

CURRENT STATE OF ART AND FUTURE

NG-CONTROLLER

IN TEMPLATE...

<div ng-app="myApp" ng-controller="myCtrl">

    First Name: <input type="text" ng-model="firstName"><br>
    Last Name: <input type="text" ng-model="lastName"><br>
    
    Full Name: {{firstName + " " + lastName}}

</div>

// myCtrl.js
angular.module('myApp', [])
    controller('myCtrl', function($scope) {
        $scope.firstName = "John";
        $scope.lastName = "Doe";
    });

  • only for very simple applications, no routing

$scope

AND NESTED SCOPE INHERITANCE PROBLEMS...

<div ng-app="myApp" ng-controller="parentCtrl">

    Parent: <input type="text" ng-model="user.name" />

    <div ng-controller="childCtrl">
        Child: <input type="text" ng-model="user.name" />
    </div>

</div>

// myCtrl.js
angular.module('myApp', [])
    .controller('parentCtrl', function($scope) {
        $scope.user = { 
            name: 'John Doe'
        }
    })
    .controller('childCtrl', function($scope) {
    });
  • scope inheritance, tight coupling, problems with debugging and reusability

$scope

AND NESTED SCOPE INHERITANCE PROBLEMS LIVE EXAMPLE...

ng Route

official but not that great routing solution

angular.module('app', []);
  
    .config(['$routeProvider', function($routeProvider) {
        $routeProvider
            .when('/someUrl', {
                templateUrl: 'some-tempalte.html',
                controller: 'SomeController',
                resolve: {}
             });
  • decouple controller from template
  • support for resolving data before entering controller
  • not flexible enough
  • impossible to nest states and views (only top level ng-view)

controller as syntax

SOLution to $scope inheritance problem

// routes.js
angular.module('app', [])
    .config(['$routeProvider', function($routeProvider) {
        $routeProvider
            .when('/someUrl', {
                templateUrl: 'some-tempalte.html',
                controller: 'SomeController as ctrl'
             });

// someController.js
angular.module('app')
    .controller('SomeController', function() {
        this.property = 'some value';
    });

// some-tempalte.html
<div>
    {{ctrl.property}}
</div>

  • properties bound directly to controller (controller's this)
  • removed dependency to $scope
  • enhanced re-usability

UI-ROUTER

THE BEGINNING OF THE GREAT ERA

$stateProvider
    .state('parent', {
        url: '/parent',
        templateUrl: 'parent.html'
    })
    .state('parent.child', {
        url: '/child',
        templateUrl: 'child.html'
    })
    .state('parent.anotherchild', {
        url: '/anotherchild',
        views: {
            '': { templateUrl: 'anotherchild.html' },
            'sidebar@anotherchild': { // ui-view="sidebar"
                template: 'Look I am a sidebar!' 
            } 
        }
    });
  • routing based on unique state name not url
  • nested states injected into ui-view directive in parent template
  • partial views

BIND TO CONTROLLER

SYNTAX THAT ENABLES CONTROLLER AS USAGE FOR DIRECTIVES WITH ISOLATE SCOPE

// some app tempalte
<div some-directive prop='value'></div>

// directive
angular.module('app', [])
    .directive('someDirective', function () {
        return {
            scope: {
                prop: '@'
            },
            templateUrl: 'someTemplate.html',
            controller: SomeController,
            controllerAs: 'ctrl',
            bindToController: true // also as of angular 1.4.1 it is possible 
                                   // to specify props {} here instead of scope   
        };
    })
    .controller('SomeController', function() {
        this.prop; // 'value'
    });

// directive tempalte: someTemplate.html
<div>{{ctrl.prop}}</div>

  • bind scope properties to controller's this automatically

COMPONENT PATTERN

FOR ANGULAR 1.X

angular
    .module('app', [])
    .directive('someComponent', someComponent);

    function someComponent() {
        return {
            restrict: 'A',
            scope: {
                // isolated scope, use to pass data from parent, eg: data: '='
            },
            controller: SomeComponent,
            controllerAs: 'ctrl',
            bindToController: true,
            templateUrl: 'some-component.tpl.html'
        };
    }

    function SomeComponent(SomeService, SomeOtherDependency) {
        this.name = 'John Doe';
    }
  • directive used to define component (controller & template)
  • encapsulated functionality, dependencies are passed explicitly

new ng router

convention over directive definition object

// app.js
angular.module('app', ['ngNewRouter'])
    .controller('AppController', ['$router', AppController]);

AppController.$routeConfig([
    {path: '/', component: 'home' }
]);
function AppController ($router) {}

// components/home/home.js
angular.module('app.home', [])
    .controller('HomeController', [function () {
        this.name = 'John Doe';
    }]);

// components/home/home.html
<h1>Hello {{home.name}}!</h1>
  • component name is specified in route definition

ANGULAR 2.0

FIRST CLASS COMPONENTS

  • component and template specified by decorators (@Component & @Template)
@Component({
    selector: 'some-component',
    componentServices: [
        SomeService
    ]
})
@Template({
    url: './some-component.html',
    directives: [Foreach] // directives used in template
})
class SomeComponent {
    constructor() {
        this.SomeService = SomeService;
        this.name= 'John Doe';
    }
    doStuff() {
        this.SomeService.doStuff();
    }
}

OK... LET'S DO THIS

MODELING VIEWS

AS A COMPONENT TREE

COMPONENT PATTERN

FOR ANGULAR 1.X - DIRECTIVE DEFINITION OBJECT

  • directive used to define component (controller & template)
// someComponent.js

// directive definition object used to specify component

function someComponent() {
    return {
        restrict: 'A', // only attribute eg: <div my-component></div>
        scope: {
            // isolated scope, use to pass data from parent, eg: data: '='
        },
        controller: SomeComponent,             // controller function
        controllerAs: 'ctrl',                  // controller alias in template
        bindToController: true,                // bind scope props to controller's this
        templateUrl: 'some-component.tpl.html' // components template url
    };
}

COMPONENT PATTERN

FOR ANGULAR 1.X - COMPONENT'S CONTROLLER

  • component's controller referenced in previously specified directive definition object
  • functionality for component's template
// someComponent.js

function SomeComponent(SomeService) {
    
    var vm = this;

    vm.name = 'John Doe';

    vm.calculate = calculate;

    function calculate(param) {
        return SomeService.performCalculation(param);
    }
}

COMPONENT PATTERN

FOR ANGULAR 1.X - COMPONENT'S TEMPLATE

  • show data and call functions exposed in component's controller
  • only data and actions of the component's controller because of isolation (directive's isolated scope and controllerAs syntax)
// some-component.tpl.html

<div>
    {{ctrl.name}}

    <button ng-click="ctrl.calculate();">Calculate!</button>
</div>

COMPONENT PATTERN

NAVIGATE TO PARTICULAR COMPONENT WITH UI-ROUTER

  • specify component as a inline template of the state
// some-component.tpl.html

angular
    .module('app', ['ui.router'])
    .config(config);

    function config($stateProvider) {
        $stateProvider
            .state('app.some', {
                url: '/some',
                template: '<div some-component></div>',
                resolve: {
                    // ... resolve data, init models
                },
            });
    }

COMPONENT PATTERN

REUSE EXISTING COMPONENTS

  • reuse component in different states
  • reuse component in template of other component
// for multiple routes
$stateProvider
    .state('app.admin.users.user', {
        url: '/app/admin/users/user/{userId}" ',
        template: '<div user-info-component></div>'
    })
    // ... other states ...
    .state('app.user.profile', {
        url: '/app/user/{userId}/profile" ',
        template: '<div user-info-component></div>'
    });

// inside of another component's template... header.tpl.html
<div>
    <!-- navigation ... -->
    <div user-info-component></div>
<div>

COMPONENT PATTERN

EASY MIGRATION TO ANGULAR 2.0 

  • use component together with ES6 syntax for extra convenient migration to Angular 2.0
  • use newNgRouter as soon as available to get rid of directive definition object
  • use ES7/TS @decorators when Angular 2.0 is available
// someComponent.js (ES6)
class SomeComponent {
    constructor(SomeService) {
        this.someProperty = 'some value accessible in template';
        this.SomeService = SomeService;
    }
 
    calculate() {
        this.SomeService.performCalculation();
    }
}

THAT'S ALL FOLKS !

I HOPE YOU ENJOYED THE PRESENTATION

PRESENTATION LINK

  • about evolution of Angular JS concepts
  • how to use them to implement Component Pattern
  • why it makes sense and how it helps with respect to future

WE LEARNED

GITHUB LINK