Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.
MEAN - Notes from the field
Chris Clarke
Hydrahack Birmingham
18th March 2014
Full-Stack Development with Javascript
• Mongo
• Express
• AngularJS
• NodeJS
http://github.com/linnovate/mean
What’s MEAN?
Who are Talis?
• MongoDB ~2.5yrs
• Using express/node ~2yrs
• Angular ~9 months
Angular AppAngular App
Typical MEAN Shape
DBDB
APIAPI
Server side
pages
Server side
pages
StaticsStatics
JSON
JSON
HTMLHTM...
Typical structure
Typical structure
Typical structure
Typical structure
Video Timeline Editor
Textbook Player
Angular 101
• Single page web app framework, by Google
• Extends HTML vocabulary to provide dynamic
views
• Broadly MVC (m...
Angular 101
• Routing
• Templates
• Controllers
• Directives
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRe...
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRe...
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRe...
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRe...
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRe...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<ul ng-show="modules!=null">
<li ng-repeat="m in modules | orderBy:'title'"
ng-class="{active:module._id==m._id}">
<a ng-h...
<input ng-model="profile.first_name" type="text" required>
<input ng-model="profile.surname" type="text" required>
<input ...
<input ng-model="profile.first_name" type="text" required>
<input ng-model="profile.surname" type="text" required>
<input ...
<input ng-model="profile.first_name" type="text" required>
<input ng-model="profile.surname" type="text" required>
<input ...
<input ng-model="profile.first_name" type="text" required>
<input ng-model="profile.surname" type="text" required>
<input ...
QuickTime™ and a
'avc1' decompressor
are needed to see this picture.
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Controllers Horizontal
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
...
Directives
<textbook-player user="user" textbook="textbook">
...
</textbook-player>
Directives
<textbook-player user="user" textbook="textbook">
...
</textbook-player>
Directives
<div user="user" textbook="textbook" textbook-player>
...
</div>
Directives
<div user="user" textbook="textbook" textbook-player>
...
</div>
angular.module('talis.directives.player.textbo...
Directives
<div user="user" textbook="textbook" textbook-player>
...
</div>
angular.module('talis.directives.player.textbo...
Directives
<div user="user" textbook="textbook" textbook-player>
...
</div>
angular.module('talis.directives.player.textbo...
Directives
<div user="user" textbook="textbook" textbook-player>
...
</div>
angular.module('talis.directives.player.textbo...
–Jonny Clientside
“Waat?”
Notes From the Field
Act I: The Basics
Elem vs. Attr directives
<textbook-player user="user" textbook="textbook">
...
</textbook-player>
<div user="user" textboo...
Minification
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
..
});
ang...
Minification
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
..
});
ang...
Mongo _id
{
_id: ObjectId(1234),
name: “Jonny Clientside”,
age: 24,
interests: [‘JQuery’,‘HTML5’
}
<a ng-href="#/people/{{...
Notes From the Field
Act II: Advanced
Angular AppAngular App
Typical MEAN Shape
DBDB
APIAPI
Server side
pages
Server side
pages
StaticsStatics
JSON
JSON
HTMLHTM...
Angular AppAngular App
JSON
9090
9090
9090
9090
Users
API
Users
API
APIAPI
Server side
pages
Server side
pages
StaticsStat...
Logging
• A lot of activity in the client side
• Some within Express/Node server side
• More behind your API proxy
var loggingModule = angular.module('talis.services.logging', []);
loggingModule.factory(
"traceService",
function(){
retur...
var loggingModule = angular.module('talis.services.logging', []);
loggingModule.factory(
"traceService",
function(){
retur...
loggingModule.factory(
"exceptionLoggingService",
["$log","$window", "traceService",
function($log, $window, traceService)...
Logging
Security
• APIs secured with OAuth 2.0 Bearer tokens
• Tokens obtained with a key/secret
• If your app is downloaded and r...
Security
• Have node return the OAuth token as JSON behind
a login barrier
• Angular requests this JSON when a route that
...
Security
• Dealing with tokens on every service call is a PITA
• Tokens expiring is normal
• Deal with it globally using a...
.run(function($rootScope,$injector) {
$injector.get("$http").defaults.transformRequest =
function(data, headersGetter) {
h...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
$httpProvider.responseInterceptors.push(
function ($rootScope, $q, $injector, $location) {
return function(promise) {
retu...
Environments
• Pretty usual to deal with prod, dev, testing
environment config on the server side
• Inject this into your ...
Environments
<script type="text/javascript" src="env/config.js"></script>
Environments
angular.module('talis.environment', [], function($provide)
constant('API_ENDPOINT', 'http://localhost:3000')....
Environments
angular.module('talis.environment', [], function($provide)
constant('API_ENDPOINT', 'https://talis.com').
con...
That’s it.
http://engineering.talis.com
We are hiring!
http://www.talis.com/jobs
@talis
facebook.com/talisgroup
+44 (0) 121 374 2740
talis.com
info@talis.com
48 Frederick Street
Birmingham
B1 3HN
MEAN - Notes from the field (Full-Stack Development with Javascript)
MEAN - Notes from the field (Full-Stack Development with Javascript)
MEAN - Notes from the field (Full-Stack Development with Javascript)
Upcoming SlideShare
Loading in …5
×

MEAN - Notes from the field (Full-Stack Development with Javascript)

0 views

Published on

Full-Stack Development with Javascript Angular/Node (AngularJS/NodeJS)

Published in: Technology
  • Be the first to comment

MEAN - Notes from the field (Full-Stack Development with Javascript)

  1. 1. MEAN - Notes from the field Chris Clarke Hydrahack Birmingham 18th March 2014 Full-Stack Development with Javascript
  2. 2. • Mongo • Express • AngularJS • NodeJS http://github.com/linnovate/mean What’s MEAN?
  3. 3. Who are Talis?
  4. 4. • MongoDB ~2.5yrs • Using express/node ~2yrs • Angular ~9 months
  5. 5. Angular AppAngular App Typical MEAN Shape DBDB APIAPI Server side pages Server side pages StaticsStatics JSON JSON HTMLHTML JSON Client side Server side
  6. 6. Typical structure
  7. 7. Typical structure
  8. 8. Typical structure
  9. 9. Typical structure
  10. 10. Video Timeline Editor
  11. 11. Textbook Player
  12. 12. Angular 101 • Single page web app framework, by Google • Extends HTML vocabulary to provide dynamic views • Broadly MVC (more accurately MVVM) • Bi-directional data binding to HTML
  13. 13. Angular 101 • Routing • Templates • Controllers • Directives
  14. 14. Routing $routeProvider.when('/modules/:module_id', { templateUrl: 'partials/module.html', controller: 'TeachCtrl', loginRequired: true, activeTab:"teach" });
  15. 15. Routing $routeProvider.when('/modules/:module_id', { templateUrl: 'partials/module.html', controller: 'TeachCtrl', loginRequired: true, activeTab:"teach" });
  16. 16. Routing $routeProvider.when('/modules/:module_id', { templateUrl: 'partials/module.html', controller: 'TeachCtrl', loginRequired: true, activeTab:"teach" });
  17. 17. Routing $routeProvider.when('/modules/:module_id', { templateUrl: 'partials/module.html', controller: 'TeachCtrl', loginRequired: true, activeTab:"teach" });
  18. 18. Routing $routeProvider.when('/modules/:module_id', { templateUrl: 'partials/module.html', controller: 'TeachCtrl', loginRequired: true, activeTab:"teach" });
  19. 19. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  20. 20. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  21. 21. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  22. 22. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  23. 23. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  24. 24. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  25. 25. <ul ng-show="modules!=null"> <li ng-repeat="m in modules | orderBy:'title'" ng-class="{active:module._id==m._id}"> <a ng-href="#/modules/{{ m._id }}">{{m.title}}</a> </li> <li> <a ng-click="add()">Add new</a> </li> </ul>
  26. 26. <input ng-model="profile.first_name" type="text" required> <input ng-model="profile.surname" type="text" required> <input ng-model="profile.email" type="email" required> <button ng-disabled="!profile.email" ng-click="update()">Update</button>
  27. 27. <input ng-model="profile.first_name" type="text" required> <input ng-model="profile.surname" type="text" required> <input ng-model="profile.email" type="email" required> <button ng-disabled="!profile.email" ng-click="update()">Update</button>
  28. 28. <input ng-model="profile.first_name" type="text" required> <input ng-model="profile.surname" type="text" required> <input ng-model="profile.email" type="email" required> <button ng-disabled="!profile.email" ng-click="update()">Update</button>
  29. 29. <input ng-model="profile.first_name" type="text" required> <input ng-model="profile.surname" type="text" required> <input ng-model="profile.email" type="email" required> <button ng-disabled="!profile.email" ng-click="update()">Update</button>
  30. 30. QuickTime™ and a 'avc1' decompressor are needed to see this picture.
  31. 31. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  32. 32. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  33. 33. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  34. 34. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  35. 35. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  36. 36. Controllers Horizontal angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { // update the profile $scope.update = function() { userSvc.updateProfile($scope.profile,function(err,profile) { if (!err) { $scope.profile = profile; } }); }) .controller('SomeOtherCtrl',....);
  37. 37. Directives <textbook-player user="user" textbook="textbook"> ... </textbook-player>
  38. 38. Directives <textbook-player user="user" textbook="textbook"> ... </textbook-player>
  39. 39. Directives <div user="user" textbook="textbook" textbook-player> ... </div>
  40. 40. Directives <div user="user" textbook="textbook" textbook-player> ... </div> angular.module('talis.directives.player.textbook', []) .directive("textbookPlayer", function() { return { restrict: "A", scope: { user: '=', entity: '=' }, controller: function($scope,textbookSvc) { // textbook logic in here } } });
  41. 41. Directives <div user="user" textbook="textbook" textbook-player> ... </div> angular.module('talis.directives.player.textbook', []) .directive("textbookPlayer", function() { return { restrict: "A", scope: { user: '=', entity: '=' }, controller: function($scope,textbookSvc) { // textbook logic in here } } });
  42. 42. Directives <div user="user" textbook="textbook" textbook-player> ... </div> angular.module('talis.directives.player.textbook', []) .directive("textbookPlayer", function() { return { restrict: "A", scope: { user: '=', entity: '=' }, controller: function($scope,textbookSvc) { // textbook logic in here } } });
  43. 43. Directives <div user="user" textbook="textbook" textbook-player> ... </div> angular.module('talis.directives.player.textbook', []) .directive("textbookPlayer", function() { return { restrict: "A", scope: { user: '=', entity: '=' }, controller: function($scope,textbookSvc) { // textbook logic in here } } });
  44. 44. –Jonny Clientside “Waat?”
  45. 45. Notes From the Field Act I: The Basics
  46. 46. Elem vs. Attr directives <textbook-player user="user" textbook="textbook"> ... </textbook-player> <div user="user" textbook="textbook" textbook-player> ... </div>
  47. 47. Minification angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { .. }); angular.module('talis.controllers.user', []) .controller('AccountCtrl',['$scope','userSvc’, function($scope, userSvc) { ... } ]);
  48. 48. Minification angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { .. }); angular.module('talis.controllers.user', []) .controller('AccountCtrl',['$scope','userSvc’, function($scope, userSvc) { ... } ]); a.m('talis.controllers.user', []) .c('AccountCtrl',['$scope','userSvc’, function(s, u) { ... } ]);
  49. 49. Mongo _id { _id: ObjectId(1234), name: “Jonny Clientside”, age: 24, interests: [‘JQuery’,‘HTML5’ } <a ng-href="#/people/{{ p._id }}">{{p.name}}</a>
  50. 50. Notes From the Field Act II: Advanced
  51. 51. Angular AppAngular App Typical MEAN Shape DBDB APIAPI Server side pages Server side pages StaticsStatics JSON JSON HTMLHTML JSON Client side Server side
  52. 52. Angular AppAngular App JSON 9090 9090 9090 9090 Users API Users API APIAPI Server side pages Server side pages StaticsStatics JSON JSON JSON Client side Meta API Meta API Files API Files API Anno API Anno API JSON DBDBDBDBDBDB DBDB RedisRedisRedisRedis HTMLHTML JSON
  53. 53. Logging • A lot of activity in the client side • Some within Express/Node server side • More behind your API proxy
  54. 54. var loggingModule = angular.module('talis.services.logging', []); loggingModule.factory( "traceService", function(){ return({ print: printStackTrace }); } ); loggingModule.provider( "$exceptionHandler",{ $get: function(exceptionLoggingService){ return(exceptionLoggingService); } } );
  55. 55. var loggingModule = angular.module('talis.services.logging', []); loggingModule.factory( "traceService", function(){ return({ print: printStackTrace }); } ); loggingModule.provider( "$exceptionHandler",{ $get: function(exceptionLoggingService){ return(exceptionLoggingService); } } );
  56. 56. loggingModule.factory( "exceptionLoggingService", ["$log","$window", "traceService", function($log, $window, traceService){ function error(exception, cause){ $log.error.apply($log, arguments); try{ var errorMessage = exception.toString(); var stackTrace = traceService.print({e: exception}); $.ajax({ type: "POST", url: "/logger", contentType: "application/json", data: angular.toJson({ url: $window.location.href, message: errorMessage, type: "exception", stackTrace: stackTrace, cause: ( cause || "") }) }); } catch (loggingError){ $log.warn("Error server-side logging failed"); $log.log(loggingError); } } return(error); }] );
  57. 57. Logging
  58. 58. Security • APIs secured with OAuth 2.0 Bearer tokens • Tokens obtained with a key/secret • If your app is downloaded and run on the client, where do you put the secret?
  59. 59. Security • Have node return the OAuth token as JSON behind a login barrier • Angular requests this JSON when a route that requires login is first requsted • If status != 200, Angular app redirects browser to login page • User logs in, repeat
  60. 60. Security • Dealing with tokens on every service call is a PITA • Tokens expiring is normal • Deal with it globally using a couple of advanced $http features
  61. 61. .run(function($rootScope,$injector) { $injector.get("$http").defaults.transformRequest = function(data, headersGetter) { headersGetter()['Authorization']="Bearer "+$rootScope.token if (data) { return angular.toJson(data); } }; });
  62. 62. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  63. 63. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  64. 64. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  65. 65. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  66. 66. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  67. 67. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  68. 68. $httpProvider.responseInterceptors.push( function ($rootScope, $q, $injector, $location) { return function(promise) { return promise.then(function(response) { return response; // no action, was successful }, function (response) { // error - was it 401 or something else? if (response.status===401 && response.data.error && response.data.error === "invalid_token") { var deferred = $q.defer(); // defer until we can re-request a new token // Get a new token... (cannot inject $http directly as will cause a circular ref) $injector.get("$http").jsonp('/some/endpoint/that/reissues/tokens?cb=JSON_CALLBACK') .then(function(loginResponse) { if (loginResponse.data) { $rootScope.oauth = loginResponse.data.oauth; // we have a new oauth token - set at $rootScope // now let's retry the original request $injector.get("$http")(response.config).then(function(response) { // we have a successful response - resolve it using deferred deferred.resolve(response); },function(response) { deferred.reject(); // something went wrong }); } else { deferred.reject(); // login.json didn't give us data } }, function(response) { deferred.reject(); // token retry failed, redirect so user can login again $location.path('/user/sign/in'); return; }); return deferred.promise; // return the deferred promise } return $q.reject(response); // not a recoverable error }); }; });
  69. 69. Environments • Pretty usual to deal with prod, dev, testing environment config on the server side • Inject this into your client side app using a dynamic JS include
  70. 70. Environments <script type="text/javascript" src="env/config.js"></script>
  71. 71. Environments angular.module('talis.environment', [], function($provide) constant('API_ENDPOINT', 'http://localhost:3000'). constant('ACTIVATE_FEATURE_FLIPS',true);
  72. 72. Environments angular.module('talis.environment', [], function($provide) constant('API_ENDPOINT', 'https://talis.com'). constant('ACTIVATE_FEATURE_FLIPS',false);
  73. 73. That’s it.
  74. 74. http://engineering.talis.com
  75. 75. We are hiring! http://www.talis.com/jobs
  76. 76. @talis facebook.com/talisgroup +44 (0) 121 374 2740 talis.com info@talis.com 48 Frederick Street Birmingham B1 3HN

×