Hugo Josefson
Senior Web Consultant at www.jayway.com
function User() {
this.sessionStore = new SessionStore();
}
var user = new User();
User constructor
controls session store impl
function User(sessionStore) {
this.sessionStore = sessionStore;
}
var user = new User(new SessionStore());
Inversion of Control (IoC)
myModule
.controller('MyController', function($http) {
$http.get('https://google.com');
});
Inject built-in AngularJS service
myModule
.value('goodUrls', { google: 'https://google.com' })
.controller('MyController', function($http, goodUrls) {
$http.get(goodUrls.google);
});
Inject own service
myModule
.value('googleDotCom', 'https://google.com')
.value('goodUrls', { google: 'https://google.com' })
.value('googleUrlGetter', function () {
return 'https://google.com';
})
.controller('MyController', function ($http, googleDotCom, goodUrls, googleUrlGetter) {
$http.get(googleDotCom);
$http.get(goodUrls.google);
$http.get(googleUrlGetter());
});
myModule
.value('googleDotCom', 'https://google.com')
.factory('goodUrls', function (googleDotCom) {
return { google: googleDotCom };
})
.factory('googleUrlGetter', function (googleDotCom) {
return function () {
return googleDotCom;
};
})
.controller('MyController', function ($http, googleDotCom, goodUrls, googleUrlGetter) {
$http.get(googleDotCom);
$http.get(goodUrls.google);
$http.get(googleUrlGetter());
});
myModule
.service('viaConstructor', function GoodUrls() {
this.google = 'https://google.com';
})
.factory('viaFactory', function goodUrlsFactory() {
return { google: 'https://google.com' };
})
.controller('MyController', function ($http, viaFactory, viaConstructor) {
$http.get(viaConstructor.google);
$http.get(viaFactory.google);
});
should be: Constructor recipe
myModule
.value('googleDotCom', 'https://google.com')
.service('viaConstructor', function GoodUrls(googleDotCom) {
this.google = googleDotCom;
})
.factory('viaFactory', function goodUrlsFactory(googleDotCom) {
return { google: googleDotCom };
})
.controller('MyController', function ($http, viaFactory, viaConstructor) {
$http.get(viaConstructor.google);
$http.get(viaFactory.google);
});
should be: Constructor recipe
angular.module('searchEngines', [])
.provider('searchEngine', function SearchEngineProvider () {
var url = null;
this.setUrl = function(newUrl) {
url = newUrl;
};
this.$get = function SearchEngineFactory($http) {
return {
search: function (query) {
$http.get(url + '?q=' + encodeURIComponent(query));
}
};
};
});
angular.module('myModule', ['searchEngines'])
.config(function (searchEngineProvider) {
searchEngineProvider.setUrl('https://google.com');
})
.controller('MyController', function (searchEngine) {
searchEngine.search('cats');
});
angular.module('searchEngines', [])
.provider('searchEngine', function SearchEngineProvider () {
var url = null;
this.setUrl = function(newUrl) {
url = newUrl;
};
this.$get = function SearchEngineFactory($http) {
return {
search: function (query) {
$http.get(url + '?q=' + encodeURIComponent(query));
}
};
};
});
angular.module('myModule', ['searchEngines'])
.constant('myUrl', 'https://google.com')
.config(function (searchEngineProvider, myUrl) {
searchEngineProvider.setUrl(myUrl);
})
.controller('MyController', function (searchEngine) {
searchEngine.search('cats');
});
.value(name, thing) // for non-injectable value, function, object
.factory(name, functionReturningThing) // for injectable value, function, object
.service(name, ConstructorForThing) // not really needed, confusing (injectable)
.provider(name, ConstructorForProvider) // for user-configurable shared service (injectable)
.constant(name, thing) // like .value(), when needed to configure Provider
var instances = 0;
myModule
.factory('myService', function() {
instances++;
return { instanceNumber: instances };
})
.run(function (myService) {
console.log('myService instance number: ', myService.instanceNumber);
})
.run(function (myService) {
console.log('myService instance number: ', myService.instanceNumber);
});
myService instance number: 1
myService instance number: 1
ng-controller
directiveng-controller
directive
<div ng-controller="MyController as my">{{ my.message }}</div>
<div ng-controller="MyController as my">{{ my.message }}</div>
var instances = 0;
myModule.controller('MyController', function() {
instances++;
this.message = 'MyController ' + instances;
});
MyController 1
MyController 2
<div ng-controller="MyController as my">
<h2>{{ my.employee.company }}</h2>
Employee name: <input ng-model="my.employee.name"/>
<button ng-click="my.save(my.employee)">Save</button>
</div>
function MyController() {
this.employee = {
name: 'Hugo',
company: 'Jayway'
};
this.save = function(person) {
alert('Save ' + person.name);
}
}
<span ng-bind="person.name">
<span ng:bind="person.name">
<span ng_bind="person.name">
<span data-ng-bind="person.name">
<span x-ng-bind="person.name">
all normalized to match directive
ngBind
<!-- Prefer: -->
<my-dir></my-dir>
<span my-dir="exp"></span>
<!-- Avoid: -->
<!-- directive: my-dir exp -->
<span class="my-dir: exp;"></span>
<svg>
<circle cx="{{cx}}"></circle>
</svg>
// Error: Invalid value for attribute cx="{{cx}}"
Avoid incomplete DOM:
<svg>
<circle ng-attr-cx="{{cx}}"></circle>
</svg>
Use ng-attr-* Directive:
myModule
.directive('jwWorldGreetingHeader', function () {
return {
restrict: 'E',
template: '<h1>Hello, World!!!</h1>'
};
});
<jw-world-greeting-header/>
<jw-world-greeting-header>
<h1>Hello, World!!!</h1>
</jw-world-greeting-header>
<h1>Hello, World!!!</h1>
Best practice:
use templateUrl
Prefix
Factory function,
returns definition obj
myModule
.directive('jwGreetingHeader', function () {
return {
restrict: 'E',
scope: {
'name': '@'
},
template: '<h1>Hello, {{ name }}!!!</h1>'
};
});
<jw-greeting-header name="Hugo"/>
<jw-greeting-header name="Hugo" class="ng-isolate-scope">
<h1 class="ng-binding">Hello, Hugo!!!</h1>
</jw-greeting-header>
<h1>Hello, Hugo!!!</h1>
isolate scope definition
myModule.directive('jwDialog', function () {
return {
restrict: 'E',
transclude: true,
scope: {
'title': '@'
},
templateUrl: 'jw-dialog.html'
};
});
<jw-dialog title="Thank you">
<p>Thank you for registering.</p>
<p>We'll be in touch.</p>
</jw-dialog>
<jw-dialog title="Thank you" class="ng-isolate-scope">
<div class="alert alert-success">
<h4 class="ng-binding">Thank you</h4>
<ng-transclude>
<p class="ng-scope">Thank you for registering.</p>
<p class="ng-scope">We'll be in touch.</p>
</ng-transclude>
</div>
</jw-dialog>
<!-- jw-dialog.html -->
<div class="alert alert-success">
<h4>{{ title }}</h4>
<ng-transclude/>
</div>
<div class="alert alert-success">
<h4>Thank you</h4>
<p>Thank you for registering.</p>
<p>We'll be in touch.</p>
</div>
Expose API from Directive
myModule.directive('jwDialog', function ($interval) {
return {
restrict: 'E',
transclude: true,
scope: { 'title': '@' },
templateUrl: 'jw-dialog.html',
link: function (scope, element, attr) {
scope.secondsPassed = 0;
var timeoutId = $interval(function() {
scope.secondsPassed++;
}, 1000);
element.on('$destroy', function() {
$interval.clear(timeoutId);
});
}
};
});
<!-- jw-dialog.html -->
<div class="alert alert-success">
<h4>{{ title }}</h4>
<ng-transclude/>
<small>Visible since {{ secondsPassed }} seconds ago...</small>
</div>
Rules of
The Angular Way
apply
<html ng-app="myApplication">
<head>
<script src="angular.js"></script>
<script src="my-application.js"></script>
...
</html>
// my-application.js
// Create module
angular.module('myApplication', []);
// Get module
var mod = angular.module('myApplication');
// Attach recipes for Service, Directive, Controller etc...
mod.value('myService', {some: 'object'});
← Different syntax!
← Different syntax!
angular.module('myApplication', [])
.value('myService', {some: 'thing'}
.controller('MyController', function(myService) {
alert(myService.some); // -> 'thing'
});
angular.module('myOtherModule', [])
.value('myService', {some: 'thing'});
angular.module('myApplication', ['myOtherModule'])
.value('myService', {what: 'ever'}); // -> SILENT name collision!
The Web, for machines
Fetch all TODO items
GET https://server.domain.tld/api/todos
Create new TODO item
POST https://server.domain.tld/api/todos
title: string, maxLength 256, required
description: string, maxLength 65536
done: boolean
Fetch single TODO item
GET https://server.domain.tld/api/todos/<id>
Update single TODO item
PUT https://server.domain.tld/api/todos/<id>
title: string, maxLength 256, required
description: string, maxLength 65536
done: boolean
Delete single TODO item
DELETE https://server.domain.tld/api/todos/<id>
EntryPoint
URL https://server.domain.tld/api
Rels to be aware of
create Creates a new item
update Saves a new version of item
delete Deletes an item
todos Returns a collection of TODO items
Expect
links in "_links",
JSON_Schema in "_links.<rel>.schema",
collection items in "_items"
AngularJS Batarang | Chrome extension |
plnkr.co, jsfiddle.net | Sketch some code |
bower.io | |
gulpjs.com, gruntjs.com | Build systems |
yeoman.io | Project scaffolding (many Generators) |
gulp-example-project, |
Per-feature directory structure examples |
ng-annotate | |
ngmodules.org |
By Hugo Josefson
Continuation of my introductory AngularJS talks. --> Get the best from Services, Controllers, Directives. --> Module structure, file/directory structure. --> Sharing code with AngularJS, non-AngularJS projects. --> REST, Hypermedia (HATEOAS).