dependency injection pattern in javascript, speakers' corner by evgeny dmitriev,...
TRANSCRIPT
DI IN JSCreated by Eugeny Dmitriev
MAY THE BROWSER BE WITH YOU.document.addEventListener("DOMContentLoaded", brewCappuccino);
Framework can provide higher abstraction$routeProvider.when('/, { templateUrl: 'home.html', controller: 'HomeCappucinoController'});
PFFF, I ALREADY HAVE MODULES.var Milk = require('milk'), CoffeeBeans = require('coffee‐beans'), Sugar = require('sugar'), Wager = require('water');
function CappuccinoFactory() { this.milk = new Milk({fat: ’skim’}); this.beans = new CoffeeBeans({from: ‘Africa’}); this.sugar = new Sugar({type: ‘granulated’}); this.water = new Water();}CappuccinoFactory.prototype.brew = function brewOneCup() { var brewedCoffee = this.water.add(this.beans.roast()).brew() var foamFoam = this.milk.add(this.sugar).heat().blend(); brewedCoffee.add(milkFoam);}
A BIT LIGHTER PATH WITH SERVICE LOCATOR.var injector = require('app').injector;
function CappuccinoFactory(injector) { this.milkFoam = injector.get(‘milkFoam’); this.brewedCoffee = injector.get(‘brewedCoffee’);}
CappuccinoFactory.prototype.brew = function brewOneCup() { brewedCoffee.add(milkFoam);}
“DON'T CALL US, WE'LL CALL YOU”function CappuccinoFactory(brewedCoffee, milkFoam) { this.milkFoam = milkFoam; this.brewedCoffee = brewedCoffee;}CappuccinoFactory.prototype.brew = function brewOneCup() { brewedCoffee.add(milkFoam);}
var BrewedCoffee = require('brewed‐coffee'), MilkFoam = require('milk‐foam');CappuccinoFactory.prototype.inject = [BrewedCoffee, MilkFoam];
ANGULAR STYLERegister bootstrap callback
Find ['ng-app']// All modules are registered including dependency per each module doBootstrap(){ createInjector(listModules, strictDi) }
// Instances dependency graph// s1// / | \// / s2 \// / / | \ \// /s3 < s4 > s5// //// s6
SYNAX// inferred (only works if code not minified/obfuscated)$injector.invoke(function(serviceA){});
// annotatedfunction explicit(serviceA) {};explicit.$inject = ['serviceA'];$injector.invoke(explicit);
// inline$injector.invoke(['serviceA', function(serviceA){}]);
AUTOMATIONnpm install ‐g ng‐annotate
// @ngInjectfunction Foo($scope) {}
function Foo($scope) {}Foo.$inject = ["$scope"];
x = /*@ngInject*/ function($scope) {};=>x = ["$scope", function($scope) {}];
npm i grunt‐ng‐annotate ‐‐save‐devnpm i gulp‐ng‐annotatenpm i browserify‐ngannotatenpm i ng‐annotate‐loader
TOMORROW COMES TODAY OR ES777// app.ats is an ATScript draft, maybe already deprecated in favor of typescriptclass MyApp { server:Server; @Bind('name') name:string; @Event('foo') fooFn:Function;
@Inject() constructor(@parent server:Server) {}
greet():string {}}
WANNA TRY?
ES6 BY DEFAULT// customer‐detail.es6import {HttpClient} from 'aurelia‐http‐client';export class CustomerDetail{ static inject() { return [HttpClient]; } constructor(http){ this.http = http; }}
Even smaller in typescript// customer‐detail.tsimport {HttpClient} from 'aurelia‐http‐client';export class CustomerDetail{ constructor(http:HttpClient){ this.http = http; }}
DI CONTAINER FEATURES// Lazy injectionimport {Lazy} from 'aurelia‐framework';import {HttpClient} from 'aurelia‐http‐client';
export class CustomerDetail{ static inject() { return [Lazy.of(HttpClient)]; } constructor(getHTTP){ this.getHTTP = getHTTP; } sendRequest(config) { getHTTP(http => { http.get('api/config', config) }) }}
DI CONTAINER FEATURES// All injection returns all initialized workersimport {Optional} from 'aurelia‐framework';import {Outsider} from 'outsiders';
export class CustomerDetail{ static inject() { return [Optional.of(Outsider)]; } constructor(outsiders){ this.outsiders = outsiders || 'no outsiders initialized'; }}
DI CONTAINER FEATURES// Parentimport {Parent} from 'aurelia‐framework';import {Router} from 'aurelia‐router';
export class CustomerDetail{ static inject() { return [Parent.of(Router)]; } constructor(router){ this.router = router; }}
DI CONTAINER FEATURES// All injection returns all initialized workersimport {All} from 'aurelia‐framework';import {Worker} from 'workers';
export class CustomerDetail{ static inject() { return [All.of(Worker)]; } constructor(workers){ this.workers = workers; }}
DI REGISTRATION FEATURES// default registration is singletonimport {Metadata} from 'aurelia‐framework';import {Woker} from 'workers';
export class CustomerDetail{ static metadata(){ return Metadata.transient(); } static inject() { return [Worker]; } constructor(worker){ this.newWorker = worker; }}
UNIT TESTINGdescribe('User permissions service specification', function () { var userServiceMock; beforeEach(module('myApp', function ($provide) { userServiceMock = { getUserDetails: jasmine.createSpy() }; $provide.value('userService', userServiceMock); })); describe('hasAdminAccess method', function () { it('should return true when user details has property: admin === true', inject(function (permissionService) { userServiceMock.getUserDetails.andReturn({admin: true}); expect(permissionService.hasAdminAccess('anAdminUser')).toBe(true); })); });});
NG-IMPROVED-TESTINGdescribe('User permissions service specification', function() { beforeEach(ModuleBuilder.forModules('myApp') .serviceWithMocksFor('permissionService', 'userService') .build()); describe('hasAdminAccess method', function() { it('should return true when user details has property: admin == true', inject(function(permissionService, userServiceMock) { userServiceMock.getUserDetails.andReturn({admin: true}); expect(permissionService.hasAdminAccess('anAdminUser')).toBe(true); })); });});
bower install ng‐improved‐testing ‐‐save‐dev
THE ENDBY EUGENY DMITRIEV