Software Engineer Frontend / Java @mimacom
// module syntax
import * as _ from 'lodash';
// classes
class MyUsefulComponent {
    constructor(someDataModel) {
        this.someDataModel = someDataModel;
    }
    
    calculate(params) {
        someDataModel.performSomeComplexCalculation(params);
    }
}
// let, const, arrow functions ...
let x = 3;
const obj = {x};
((obj) => {
    console.log(obj.x); // 3
})(obj);
COMPONENT PATTERN
COMPONENT PATTERN
COMPONENT PATTERN
<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";
    });
COMPONENT PATTERN
<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) {
    });COMPONENT PATTERN
COMPONENT PATTERN
COMPONENT PATTERN
angular.module('app', []);
  
    .config(['$routeProvider', function($routeProvider) {
        $routeProvider
            .when('/someUrl', {
                templateUrl: 'some-tempalte.html',
                controller: 'SomeController',
                resolve: {}
             });COMPONENT PATTERN
// 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>
COMPONENT PATTERN
$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!' 
            } 
        }
    });COMPONENT PATTERN
// 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>
COMPONENT PATTERN
The component model for the Web (also known as Web Components) consists of pieces designed to be used together to let web application authors define widgets with a level of visual richness not possible with CSS alone, and ease of composition and reuse not possible with script libraries today
<bootstrap-navbar sticky></bootstrap-navbar>
<section class="container" role="main">  
  <!-- ... -->
</section>   
<bootstrap-footer></bootstrap-footer> 
COMPONENT PATTERN
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';
    }COMPONENT PATTERN
// 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 PATTERN
@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();
    }
}COMPONENT PATTERN
COMPONENT PATTERN
COMPONENT PATTERN
// 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
// someComponent.js
// @ngInject
function SomeComponent(SomeService) {
    this.name = 'John Doe';
    this.calculate= calculate;
    function calculate(param) {
        return SomeService.performCalculation(param);
    }
}COMPONENT PATTERN
// some-component.tpl.html
<div>
    {{ctrl.name}}
    <button ng-click="ctrl.calculate();">Calculate!</button>
</div>COMPONENT PATTERN
// some-component.tpl.html
angular
    .module('app', ['ui.router'])
    .config(config);
    // @ngInject
    function config($stateProvider) {
        $stateProvider
            .state('app.some', {
                url: '/some',
                template: '<div some-component></div>',
                resolve: {
                    // ... resolve data, init models
                },
            });
    }COMPONENT PATTERN
// 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
// someComponent.js (ES6)
class SomeComponent {
    constructor(SomeService) {
        this.SomeService = SomeService;
    }
 
    calculate() {
        this.SomeService.performCalculation();
    }
}MODEL PATTERN
angular.module('app', [])
    .controller('MyOvercompetentController', function($http) {
        init();
        function init() {
            $http.get('/api/my/data/endpoint').then(function(response) {
                $scope.data = response.data;
            });
        }
        $scope.add = function(newObject) {
            $scope.data.push(newObject);
        };
    });MODEL PATTERN
function SomeService($http) {
    var data = [];
    return {
        data: data,
        findAll: findAll 
    }
    function findAll() {
        return $http.get('/some').then(function(response) {
            angular.copy(response.data, data); // angular.copy to preserve reference
        });
    }
 
}
 
function SomeController(SomeService) {
    var ctrl = this;
    ctrl.data = SomeService.data; // bind model to controller (create reference)
}
 
<div ng-repeat="d in ctrl.data">
     {{d.property}}
</div>MODEL PATTERN
class BookModel {
 
    constructor($q, BookRestResource) {
        this.BookRestResource = BookRestResource;
   
        this.collection = [];
        this.item = {};
    }
 
    initCollection() {
        return this.BookRestResource.findAll().then((books) => {
            
            /* 
                response.data is unwrapped centrally in   
                httpInterceptor for every successful request
            */
            this.collection = books;
 
        });
    }
 
    initItem(bookId) { 
 
        // init item from backend
        if (bookId) {
            return this.BookRestResource.findById(bookId)
                .then((book) => {
                    this.item = book;
                });
 
        // init new item 
        } else {
            this.item = { 
                // init some default values if necessary eg:
                property: 'defaultValue'
            };
 
            /*
                we return and resolve helper promise to assure
                consistent API of method so that we can always
                use .then() method when calling initItem
            */
            return $q.resolve();
        }
    }
 
    save() {
        /*
            update existing item if model contains id 
            (could contain more complex checking logic based
             model's specific needs)
        */
        if (this.item.id) {
            return this.BookRestResource.update(this.item);
        } else {
            /*
               we use CQRS approach in backend
               (no side effects for GET and no return
               value for POST, PUT, DELETE) so we have 
               to refresh model manually when needed
               
               only exception to this rule is returning id 
               when creating new entity so that we can 
               refresh item model with newly created item
            */    
            return this.BookRestResource.create(this.item)
                .then((bookId) => { 
                    return bookId;
                });
        }
    }
 
    // example of business method
    filterByAuthorName(authorName) {
        return _.filter(this.collection, (book) => {
             return book.authorName === authorName;
        });  
    }
 
}MODEL PATTERN
$stateProvider
    .state('app.offer.item', {
        url: '/item/:itemId',
        template: '<div item-component></div>',
        resolve: {
            // doesn't return data, just initialize model which are used by components
            init: function($stateParams, ItemModel, DiscountModel) {
                // parallel / no dependency
                return $q.all([
                    DiscountModel.initCollection(),
                    ItemModel.initItem($stateParams.itemId)
                ]);
                // ... or serial / dependency between models
                return ItemModel.initItem($stateParams.itemId)
                    .then(function(item) {
                        return DiscountModel.initCollection(item.category);
                    });
            }
        }
    });MODEL PATTERN
// component's controller
class ItemDetailComponent {
    constructor(ItemModel, DiscountModel) {
        this.ItemModel = ItemModel;
        this.DiscountModel = DiscountModel;
    }
    calculate() {
        return this.ItemModel.doSomeBusinessProcessing();
    }
}
// component's tempalte
<div>
    Product: {{ctrl.ItemModel.item.name}} <br />
    Price: {{ctrl.ItemModel.item.price}}
</div>
<div ng-repeat="discount in ctrl.DiscountModel.collection">
     <p>{{discount.info}}</p>
</div>
<button ng-click="calculate()">Calculate</button>MODEL PATTERN
MODEL PATTERN
class NameSpacedModel {
 
    constructor($q, SomeRestResource) {
        this.SomeRestResource = SomeRestResource;
   
        this.models = {
            main: {
                collection: [],
                item: {}
            }
        };
    }
     
    // convenience getters for accessing main model directly
    get collection() {
        return this.models.main.collection;
    }
 
    get item() {
        return this.models.main.item;
    }
 
    initCollection(modelId) {
        return this.SomeRestResource.findAll().then((data) => {
            return this.getModel(modelId).collection = data;
        });
    }
 
    // helper method for accessing desired model namespace
    getModel(modelId = 'main') {
        this.models[modelId] = this.models[modelId] ?  
            this.models[modelId] : 
            { collection: [], item: {} };
        return this.models[modelId];
    }
 
}https://slides.com/tomastrajan/component-and-model-pattern-for-angular-js