SlideShare une entreprise Scribd logo
1  sur  79
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
HTMLHTML
JSON
Client side
Server side
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 (more accurately MVVM)
• Bi-directional data binding to HTML
Angular 101
• Routing
• Templates
• Controllers
• Directives
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRequired: true,
activeTab:"teach"
});
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRequired: true,
activeTab:"teach"
});
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRequired: true,
activeTab:"teach"
});
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRequired: true,
activeTab:"teach"
});
Routing
$routeProvider.when('/modules/:module_id', {
templateUrl: 'partials/module.html',
controller: 'TeachCtrl',
loginRequired: true,
activeTab:"teach"
});
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
<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>
QuickTime™ and a
'avc1' decompressor
are needed to see this picture.
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',....);
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',....);
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',....);
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',....);
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',....);
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',....);
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.textbook', [])
.directive("textbookPlayer", function() {
return {
restrict: "A",
scope: {
user: '=',
entity: '='
},
controller: function($scope,textbookSvc) {
// textbook logic in here
}
}
});
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
}
}
});
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
}
}
});
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
}
}
});
–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" textbook="textbook" textbook-player>
...
</div>
Minification
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',function($scope, userSvc) {
..
});
angular.module('talis.controllers.user', [])
.controller('AccountCtrl',['$scope','userSvc’,
function($scope, userSvc) {
...
}
]);
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) {
...
}
]);
Mongo _id
{
_id: ObjectId(1234),
name: “Jonny Clientside”,
age: 24,
interests: [‘JQuery’,‘HTML5’
}
<a ng-href="#/people/{{ p._id }}">{{p.name}}</a>
Notes From the Field
Act II: Advanced
Angular AppAngular App
Typical MEAN Shape
DBDB
APIAPI
Server side
pages
Server side
pages
StaticsStatics
JSON
JSON
HTMLHTML
JSON
Client side
Server side
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
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(){
return({
print: printStackTrace
});
}
);
loggingModule.provider(
"$exceptionHandler",{
$get: function(exceptionLoggingService){
return(exceptionLoggingService);
}
}
);
var loggingModule = angular.module('talis.services.logging', []);
loggingModule.factory(
"traceService",
function(){
return({
print: printStackTrace
});
}
);
loggingModule.provider(
"$exceptionHandler",{
$get: function(exceptionLoggingService){
return(exceptionLoggingService);
}
}
);
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);
}]
);
Logging
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?
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
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
.run(function($rootScope,$injector) {
$injector.get("$http").defaults.transformRequest =
function(data, headersGetter) {
headersGetter()['Authorization']="Bearer "+$rootScope.token
if (data) {
return angular.toJson(data);
}
};
});
$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
});
};
});
$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
});
};
});
$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
});
};
});
$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
});
};
});
$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
});
};
});
$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
});
};
});
$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
});
};
});
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
Environments
<script type="text/javascript" src="env/config.js"></script>
Environments
angular.module('talis.environment', [], function($provide)
constant('API_ENDPOINT', 'http://localhost:3000').
constant('ACTIVATE_FEATURE_FLIPS',true);
Environments
angular.module('talis.environment', [], function($provide)
constant('API_ENDPOINT', 'https://talis.com').
constant('ACTIVATE_FEATURE_FLIPS',false);
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

Contenu connexe

Tendances

Dart and AngularDart
Dart and AngularDartDart and AngularDart
Dart and AngularDartLoc Nguyen
 
Dive into AngularJS Routing
Dive into AngularJS RoutingDive into AngularJS Routing
Dive into AngularJS RoutingEgor Miasnikov
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSVisual Engineering
 
Angular JS Routing
Angular JS RoutingAngular JS Routing
Angular JS Routingkennystoltz
 
AngularJS Directives
AngularJS DirectivesAngularJS Directives
AngularJS DirectivesEyal Vardi
 
AngularJS Routing
AngularJS RoutingAngularJS Routing
AngularJS RoutingEyal Vardi
 
Introduction to AJAX In WordPress
Introduction to AJAX In WordPressIntroduction to AJAX In WordPress
Introduction to AJAX In WordPressCaldera Labs
 
Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonFokke Zandbergen
 
How routing works in angular js
How routing works in angular jsHow routing works in angular js
How routing works in angular jscodeandyou forums
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsMike Subelsky
 
Stratalux Cloud Formation and Chef Integration Presentation
Stratalux Cloud Formation and Chef Integration PresentationStratalux Cloud Formation and Chef Integration Presentation
Stratalux Cloud Formation and Chef Integration PresentationJeremy Przygode
 
Introducing AngularJS
Introducing AngularJSIntroducing AngularJS
Introducing AngularJSLoc Nguyen
 
ui-router and $state
ui-router and $stateui-router and $state
ui-router and $stategarbles
 
Beyond DOMReady: Ultra High-Performance Javascript
Beyond DOMReady: Ultra High-Performance JavascriptBeyond DOMReady: Ultra High-Performance Javascript
Beyond DOMReady: Ultra High-Performance Javascriptaglemann
 
Angular JS blog tutorial
Angular JS blog tutorialAngular JS blog tutorial
Angular JS blog tutorialClaude Tech
 
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...apidays
 
AngularJS with Slim PHP Micro Framework
AngularJS with Slim PHP Micro FrameworkAngularJS with Slim PHP Micro Framework
AngularJS with Slim PHP Micro FrameworkBackand Cohen
 
MVVM with SwiftUI and Combine
MVVM with SwiftUI and CombineMVVM with SwiftUI and Combine
MVVM with SwiftUI and CombineTai Lun Tseng
 

Tendances (20)

Dart and AngularDart
Dart and AngularDartDart and AngularDart
Dart and AngularDart
 
Dive into AngularJS Routing
Dive into AngularJS RoutingDive into AngularJS Routing
Dive into AngularJS Routing
 
Workshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJSWorkshop 27: Isomorphic web apps with ReactJS
Workshop 27: Isomorphic web apps with ReactJS
 
Angular JS Routing
Angular JS RoutingAngular JS Routing
Angular JS Routing
 
AngularJS Directives
AngularJS DirectivesAngularJS Directives
AngularJS Directives
 
AngularJS Routing
AngularJS RoutingAngularJS Routing
AngularJS Routing
 
Introduction to AJAX In WordPress
Introduction to AJAX In WordPressIntroduction to AJAX In WordPress
Introduction to AJAX In WordPress
 
Alloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLonAlloy Tips & Tricks #TiLon
Alloy Tips & Tricks #TiLon
 
How routing works in angular js
How routing works in angular jsHow routing works in angular js
How routing works in angular js
 
SproutCore and the Future of Web Apps
SproutCore and the Future of Web AppsSproutCore and the Future of Web Apps
SproutCore and the Future of Web Apps
 
Stratalux Cloud Formation and Chef Integration Presentation
Stratalux Cloud Formation and Chef Integration PresentationStratalux Cloud Formation and Chef Integration Presentation
Stratalux Cloud Formation and Chef Integration Presentation
 
Sane Async Patterns
Sane Async PatternsSane Async Patterns
Sane Async Patterns
 
Introducing AngularJS
Introducing AngularJSIntroducing AngularJS
Introducing AngularJS
 
ui-router and $state
ui-router and $stateui-router and $state
ui-router and $state
 
Beyond DOMReady: Ultra High-Performance Javascript
Beyond DOMReady: Ultra High-Performance JavascriptBeyond DOMReady: Ultra High-Performance Javascript
Beyond DOMReady: Ultra High-Performance Javascript
 
Node.js server-side rendering
Node.js server-side renderingNode.js server-side rendering
Node.js server-side rendering
 
Angular JS blog tutorial
Angular JS blog tutorialAngular JS blog tutorial
Angular JS blog tutorial
 
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...
APIdays Zurich 2019 - Specification Driven Development for REST APIS Alexande...
 
AngularJS with Slim PHP Micro Framework
AngularJS with Slim PHP Micro FrameworkAngularJS with Slim PHP Micro Framework
AngularJS with Slim PHP Micro Framework
 
MVVM with SwiftUI and Combine
MVVM with SwiftUI and CombineMVVM with SwiftUI and Combine
MVVM with SwiftUI and Combine
 

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

Introduction to AngularJS For WordPress Developers
Introduction to AngularJS For WordPress DevelopersIntroduction to AngularJS For WordPress Developers
Introduction to AngularJS For WordPress DevelopersCaldera Labs
 
Coffee@DBG - Exploring Angular JS
Coffee@DBG - Exploring Angular JSCoffee@DBG - Exploring Angular JS
Coffee@DBG - Exploring Angular JSDeepu S Nath
 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto UniversitySC5.io
 
Angular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersAngular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersOswald Campesato
 
AngularJs Superheroic JavaScript MVW Framework Services by Miracle Studios
AngularJs Superheroic JavaScript MVW Framework Services by Miracle StudiosAngularJs Superheroic JavaScript MVW Framework Services by Miracle Studios
AngularJs Superheroic JavaScript MVW Framework Services by Miracle StudiosLearnimtactics
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaExoLeaders.com
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsFrancois Zaninotto
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For ManagersAgileThought
 
Introducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverIntroducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverSpike Brehm
 
Client-side Rendering with AngularJS
Client-side Rendering with AngularJSClient-side Rendering with AngularJS
Client-side Rendering with AngularJSDavid Lapsley
 
Introduction of angular js
Introduction of angular jsIntroduction of angular js
Introduction of angular jsTamer Solieman
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejsNick Lee
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJSWei Ru
 

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

Introduction to AngularJS For WordPress Developers
Introduction to AngularJS For WordPress DevelopersIntroduction to AngularJS For WordPress Developers
Introduction to AngularJS For WordPress Developers
 
Coffee@DBG - Exploring Angular JS
Coffee@DBG - Exploring Angular JSCoffee@DBG - Exploring Angular JS
Coffee@DBG - Exploring Angular JS
 
Angular.js Primer in Aalto University
Angular.js Primer in Aalto UniversityAngular.js Primer in Aalto University
Angular.js Primer in Aalto University
 
Angular1x and Angular 2 for Beginners
Angular1x and Angular 2 for BeginnersAngular1x and Angular 2 for Beginners
Angular1x and Angular 2 for Beginners
 
AngularJs Superheroic JavaScript MVW Framework Services by Miracle Studios
AngularJs Superheroic JavaScript MVW Framework Services by Miracle StudiosAngularJs Superheroic JavaScript MVW Framework Services by Miracle Studios
AngularJs Superheroic JavaScript MVW Framework Services by Miracle Studios
 
AngularJS.part1
AngularJS.part1AngularJS.part1
AngularJS.part1
 
Angular
AngularAngular
Angular
 
Angular
AngularAngular
Angular
 
Good karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with KarmaGood karma: UX Patterns and Unit Testing in Angular with Karma
Good karma: UX Patterns and Unit Testing in Angular with Karma
 
Angular js
Angular jsAngular js
Angular js
 
Bonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node jsBonnes pratiques de développement avec Node js
Bonnes pratiques de développement avec Node js
 
Introduction to AngularJS
Introduction to AngularJSIntroduction to AngularJS
Introduction to AngularJS
 
Patterns Are Good For Managers
Patterns Are Good For ManagersPatterns Are Good For Managers
Patterns Are Good For Managers
 
Introducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and serverIntroducing Rendr: Run your Backbone.js apps on the client and server
Introducing Rendr: Run your Backbone.js apps on the client and server
 
AngularJS Workshop
AngularJS WorkshopAngularJS Workshop
AngularJS Workshop
 
AngularJS
AngularJSAngularJS
AngularJS
 
Client-side Rendering with AngularJS
Client-side Rendering with AngularJSClient-side Rendering with AngularJS
Client-side Rendering with AngularJS
 
Introduction of angular js
Introduction of angular jsIntroduction of angular js
Introduction of angular js
 
Understanding backbonejs
Understanding backbonejsUnderstanding backbonejs
Understanding backbonejs
 
Practical AngularJS
Practical AngularJSPractical AngularJS
Practical AngularJS
 

Plus de Chris Clarke

Using MongoDB as a high performance graph database
Using MongoDB as a high performance graph databaseUsing MongoDB as a high performance graph database
Using MongoDB as a high performance graph databaseChris Clarke
 
Programme Update, Talis Aspire User Group Jun 2011
Programme Update, Talis Aspire User Group Jun 2011Programme Update, Talis Aspire User Group Jun 2011
Programme Update, Talis Aspire User Group Jun 2011Chris Clarke
 
Linking Education Data
Linking Education DataLinking Education Data
Linking Education DataChris Clarke
 
Linked Open Courseware
Linked Open CoursewareLinked Open Courseware
Linked Open CoursewareChris Clarke
 
Using Linked Data as the basis for Learning Resource Recommendation
Using Linked Data as the basis for Learning Resource RecommendationUsing Linked Data as the basis for Learning Resource Recommendation
Using Linked Data as the basis for Learning Resource RecommendationChris Clarke
 
A Resource List Management Tool based on Linked Open Data Principles
A Resource List Management Tool based on Linked Open Data PrinciplesA Resource List Management Tool based on Linked Open Data Principles
A Resource List Management Tool based on Linked Open Data PrinciplesChris Clarke
 
Aspire Days Intro - Northumbria University 13th May
Aspire Days Intro - Northumbria University 13th MayAspire Days Intro - Northumbria University 13th May
Aspire Days Intro - Northumbria University 13th MayChris Clarke
 
Aspire Days Roadmap - Northumbria University 13th May
Aspire Days Roadmap - Northumbria University 13th MayAspire Days Roadmap - Northumbria University 13th May
Aspire Days Roadmap - Northumbria University 13th MayChris Clarke
 
Bringing eContent to Life
Bringing eContent to LifeBringing eContent to Life
Bringing eContent to LifeChris Clarke
 
Xiphos Network: Building the scholarly web of data
Xiphos Network: Building the scholarly web of dataXiphos Network: Building the scholarly web of data
Xiphos Network: Building the scholarly web of dataChris Clarke
 

Plus de Chris Clarke (10)

Using MongoDB as a high performance graph database
Using MongoDB as a high performance graph databaseUsing MongoDB as a high performance graph database
Using MongoDB as a high performance graph database
 
Programme Update, Talis Aspire User Group Jun 2011
Programme Update, Talis Aspire User Group Jun 2011Programme Update, Talis Aspire User Group Jun 2011
Programme Update, Talis Aspire User Group Jun 2011
 
Linking Education Data
Linking Education DataLinking Education Data
Linking Education Data
 
Linked Open Courseware
Linked Open CoursewareLinked Open Courseware
Linked Open Courseware
 
Using Linked Data as the basis for Learning Resource Recommendation
Using Linked Data as the basis for Learning Resource RecommendationUsing Linked Data as the basis for Learning Resource Recommendation
Using Linked Data as the basis for Learning Resource Recommendation
 
A Resource List Management Tool based on Linked Open Data Principles
A Resource List Management Tool based on Linked Open Data PrinciplesA Resource List Management Tool based on Linked Open Data Principles
A Resource List Management Tool based on Linked Open Data Principles
 
Aspire Days Intro - Northumbria University 13th May
Aspire Days Intro - Northumbria University 13th MayAspire Days Intro - Northumbria University 13th May
Aspire Days Intro - Northumbria University 13th May
 
Aspire Days Roadmap - Northumbria University 13th May
Aspire Days Roadmap - Northumbria University 13th MayAspire Days Roadmap - Northumbria University 13th May
Aspire Days Roadmap - Northumbria University 13th May
 
Bringing eContent to Life
Bringing eContent to LifeBringing eContent to Life
Bringing eContent to Life
 
Xiphos Network: Building the scholarly web of data
Xiphos Network: Building the scholarly web of dataXiphos Network: Building the scholarly web of data
Xiphos Network: Building the scholarly web of data
 

Dernier

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slidevu2urc
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...apidays
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024The Digital Insurer
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdflior mazor
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationRadu Cotescu
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)Gabriella Davis
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobeapidays
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptxHampshireHUG
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...apidays
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century educationjfdjdjcjdnsjd
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationMichael W. Hawkins
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProduct Anonymous
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherRemote DBA Services
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...DianaGray10
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)wesley chun
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilV3cube
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAndrey Devyatkin
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CVKhem
 

Dernier (20)

Histor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slideHistor y of HAM Radio presentation slide
Histor y of HAM Radio presentation slide
 
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law DevelopmentsTrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
TrustArc Webinar - Stay Ahead of US State Data Privacy Law Developments
 
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
Apidays Singapore 2024 - Building Digital Trust in a Digital Economy by Veron...
 
Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024Finology Group – Insurtech Innovation Award 2024
Finology Group – Insurtech Innovation Award 2024
 
GenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdfGenAI Risks & Security Meetup 01052024.pdf
GenAI Risks & Security Meetup 01052024.pdf
 
Scaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organizationScaling API-first – The story of a global engineering organization
Scaling API-first – The story of a global engineering organization
 
A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)A Domino Admins Adventures (Engage 2024)
A Domino Admins Adventures (Engage 2024)
 
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
+971581248768>> SAFE AND ORIGINAL ABORTION PILLS FOR SALE IN DUBAI AND ABUDHA...
 
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, AdobeApidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
Apidays New York 2024 - Scaling API-first by Ian Reasor and Radu Cotescu, Adobe
 
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
04-2024-HHUG-Sales-and-Marketing-Alignment.pptx
 
Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...Apidays New York 2024 - The value of a flexible API Management solution for O...
Apidays New York 2024 - The value of a flexible API Management solution for O...
 
presentation ICT roal in 21st century education
presentation ICT roal in 21st century educationpresentation ICT roal in 21st century education
presentation ICT roal in 21st century education
 
GenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day PresentationGenCyber Cyber Security Day Presentation
GenCyber Cyber Security Day Presentation
 
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemkeProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
ProductAnonymous-April2024-WinProductDiscovery-MelissaKlemke
 
Strategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a FresherStrategies for Landing an Oracle DBA Job as a Fresher
Strategies for Landing an Oracle DBA Job as a Fresher
 
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
Connector Corner: Accelerate revenue generation using UiPath API-centric busi...
 
Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)Powerful Google developer tools for immediate impact! (2023-24 C)
Powerful Google developer tools for immediate impact! (2023-24 C)
 
Developing An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of BrazilDeveloping An App To Navigate The Roads of Brazil
Developing An App To Navigate The Roads of Brazil
 
AWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of TerraformAWS Community Day CPH - Three problems of Terraform
AWS Community Day CPH - Three problems of Terraform
 
Real Time Object Detection Using Open CV
Real Time Object Detection Using Open CVReal Time Object Detection Using Open CV
Real Time Object Detection Using Open CV
 

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

  • 1. MEAN - Notes from the field Chris Clarke Hydrahack Birmingham 18th March 2014 Full-Stack Development with Javascript
  • 2.
  • 3.
  • 4. • Mongo • Express • AngularJS • NodeJS http://github.com/linnovate/mean What’s MEAN?
  • 6. • MongoDB ~2.5yrs • Using express/node ~2yrs • Angular ~9 months
  • 7. Angular AppAngular App Typical MEAN Shape DBDB APIAPI Server side pages Server side pages StaticsStatics JSON JSON HTMLHTML JSON Client side Server side
  • 14. 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
  • 15. Angular 101 • Routing • Templates • Controllers • Directives
  • 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. <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. <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. <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. <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. <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>
  • 27. <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>
  • 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. <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. <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>
  • 31. <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>
  • 32. QuickTime™ and a 'avc1' decompressor are needed to see this picture.
  • 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. 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. 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. 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. 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',....);
  • 38. 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',....);
  • 41. Directives <div user="user" textbook="textbook" textbook-player> ... </div>
  • 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. 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. 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 } } });
  • 45. 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 } } });
  • 47. Notes From the Field Act I: The Basics
  • 48. Elem vs. Attr directives <textbook-player user="user" textbook="textbook"> ... </textbook-player> <div user="user" textbook="textbook" textbook-player> ... </div>
  • 49. Minification angular.module('talis.controllers.user', []) .controller('AccountCtrl',function($scope, userSvc) { .. }); angular.module('talis.controllers.user', []) .controller('AccountCtrl',['$scope','userSvc’, function($scope, userSvc) { ... } ]);
  • 50. 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) { ... } ]);
  • 51. Mongo _id { _id: ObjectId(1234), name: “Jonny Clientside”, age: 24, interests: [‘JQuery’,‘HTML5’ } <a ng-href="#/people/{{ p._id }}">{{p.name}}</a>
  • 52. Notes From the Field Act II: Advanced
  • 53. Angular AppAngular App Typical MEAN Shape DBDB APIAPI Server side pages Server side pages StaticsStatics JSON JSON HTMLHTML JSON Client side Server side
  • 54. 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
  • 55. Logging • A lot of activity in the client side • Some within Express/Node server side • More behind your API proxy
  • 56. var loggingModule = angular.module('talis.services.logging', []); loggingModule.factory( "traceService", function(){ return({ print: printStackTrace }); } ); loggingModule.provider( "$exceptionHandler",{ $get: function(exceptionLoggingService){ return(exceptionLoggingService); } } );
  • 57. var loggingModule = angular.module('talis.services.logging', []); loggingModule.factory( "traceService", function(){ return({ print: printStackTrace }); } ); loggingModule.provider( "$exceptionHandler",{ $get: function(exceptionLoggingService){ return(exceptionLoggingService); } } );
  • 58. 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); }] );
  • 59.
  • 61. 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?
  • 62. 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
  • 63. 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
  • 64. .run(function($rootScope,$injector) { $injector.get("$http").defaults.transformRequest = function(data, headersGetter) { headersGetter()['Authorization']="Bearer "+$rootScope.token if (data) { return angular.toJson(data); } }; });
  • 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. $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. $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. $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. $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 }); }; });
  • 70. $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 }); }; });
  • 71. $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 }); }; });
  • 72. 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
  • 74. Environments angular.module('talis.environment', [], function($provide) constant('API_ENDPOINT', 'http://localhost:3000'). constant('ACTIVATE_FEATURE_FLIPS',true);
  • 75. Environments angular.module('talis.environment', [], function($provide) constant('API_ENDPOINT', 'https://talis.com'). constant('ACTIVATE_FEATURE_FLIPS',false);
  • 79. @talis facebook.com/talisgroup +44 (0) 121 374 2740 talis.com info@talis.com 48 Frederick Street Birmingham B1 3HN

Notes de l'éditeur

  1. Anyone been playing this?
  2. WTF?
  3. Check out the score - weird! Back to work…
  4. Whole stack is JS, from the DB, the native data format (BSON), the server side and the front end. Even your DB queries are JSON and the DB client for debugging
  5. Birmingham EdTech learning platform 65 unis 1 million students 50% UK + other campuses on 3 continents ~15M hits per week page/api Legacy in PHP
  6. Node app here.
  7. Angular app here
  8. Note the node app serves index.html which includes app.js which bootstraps the angular app
  9. Some things we are building with MEAN Timeline editing UI
  10. Textbooks Copy and paste from OCRed images Rich annotations + notes In-book search
  11. Bi directional is AWESOME! Whistle stop tour
  12. Bi directional is AWESOME! Whistle stop tour
  13. Pretty familiar
  14. bind to params in URL
  15. our HTML template
  16. the controller
  17. can add extra properties to the route
  18. update() is a method in the CONTROLLER
  19. Does this via event loop
  20. dependancy injection $scope and userSvc are singletons
  21. SERVICES - generally logic for persisting models or other interactions with the server side
  22. wrapping behavior and logic to specific tags, independent from view-level controller independent scope, many on same view great for re-use
  23. user and textbook passed as parameters from view scope
  24. More common to use as attribute name
  25. namespace
  26. directive function
  27. view scope -&amp;gt; directive scope (user/entity)
  28. directive controller - $scope is injected by framework
  29. If you’re a client side developer then Angular comes as a shock Actually if you’re a server side guy you’ll be more at home Resist JQuery
  30. First some common gotchas
  31. For IE compat use attribute
  32. Dependancy injection Minification will shorten $scope, userSvc Array param will assume last param func, preceeding params strings mapping to vars
  33. End result
  34. Caught us out moving to 1.2 Angular assumes all _ properties are private PITA in templates Backed out in later versions
  35. You can find these on our blog
  36. Remember this?
  37. Talis App shape - SOA Proxy API CORS ripple of change protection - what version of angular app is it? api versioning
  38. Angular has built in $exceptionHandler that will log to console :-/
  39. Why are we using $.ajax?
  40. Collect more feedback from user Propigate unexpected errors up to $rootScope Form in your master template
  41. Logstash/ElasticSearch/Kibana Consider watermarking log statements through stack using an ID
  42. https!!
  43. transformRequest in your app’s run method adds the token (where available) to every single request made with $http
  44. Dealing with token expiry and re-request responseInterceptor
  45. Dealing with token expiry and re-request responseInterceptor
  46. Dealing with token expiry and re-request responseInterceptor
  47. Dealing with token expiry and re-request responseInterceptor
  48. Dealing with token expiry and re-request responseInterceptor
  49. Dealing with token expiry and re-request responseInterceptor
  50. Dealing with token expiry and re-request responseInterceptor
  51. In your main page
  52. Dynamically generated by a service in node constants available anywhere by dependancy injection
  53. in production
  54. You can find these on our blog