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