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

Component pattern for Angular JS (1.X)

By Tomáš Trajan

Component pattern for Angular JS (1.X)

How and why to implement Component Pattern in Angular JS 1.X. Lightning overview of evolution of Angular JS concepts, Component Pattern implementation, migration path to Angular 2.0

  • 7,417