SlideShare a Scribd company logo
1 of 88
Download to read offline
Design Strategies with 
AngularJS 
1
Design Strategies with 
AngularJS 
1 
Somik Raha
Design Strategies with 
AngularJS 
Somik Raha Kai Wu 
1
Design Strategies with 
AngularJS 
Somik Raha Kai Wu 
1 
#smartorgdev
Rip Van Winkle 
Slept through 
the American 
Revolution 
Slept off in 2004 
and woke up in 
2014 2
3
2004… 
3
2004… 
3
2004… 
C++ 
3
2004… 
C++ Java 
3
2004… 
C++ Java 
Microsoft 
3
2004… 
C++ Java 
Microsoft Sun 
3
2004… 
C++ Java 
Microsoft Sun 
#smartorgdev 3
2014. 
C++ Java 
Microsoft Sun 
#smartorgdev 4
2014. 
Javascript 
#smartorgdev 4
Backend 
#smartorgdev 5
Backend Middleware 
#smartorgdev 5
Backend Middleware Front-end 
#smartorgdev 5
Backend Middleware Front-end 
#smartorgdev 5
Backend Middleware Front-end 
#smartorgdev 6
Super-heroic Javascript framework 
#smartorgdev 7
Super-heroic Javascript framework 
#smartorgdev 7
First reaction 
I don’t get it. Backbone is great for us. 
#smartorgdev 8
Second reaction 
Whoa!! 
#smartorgdev 9
Backbone learning curve AngularJS learning curve 
Flattens quickly 
Minimal concepts 
10 
Service 
Directives 
Controllers 
and views 
Staircase 
… 
!
Backbone learning curve AngularJS learning curve 
Flattens quickly 
Minimal concepts 
10 
Ben Nadel’s blog
Stats comparing Backbone 
with AngularJS 
AppStructure Wizard 
#smartorgdev 11
Stats comparing Backbone 
with AngularJS 
AppStructure Wizard 
#smartorgdev 11
Stats comparing Backbone 
with AngularJS 
AppStructure Wizard 
#smartorgdev 11
Three Kinds of Scenarios 
Single Page 
Application 
Multi-Page 
Application 
Legacy 
Application 
Classic Sophisticated Convoluted 
#smartorgdev 12
Multi-Page Application 
Browser loads entire page 
e.g. Login 
e.g. Show 
Projects 
Page 1 Page 2 Page 3 
View 1 View 2 View 3 
Controller 1 Controller 2 Controller 3 
Route 1 Route 2 Route 3 
/login /showProjects /… 
Map routes to controllers and views 
#smartorgdev 13
angular.module('myApp', [ 
'ngRoute', 
… 
]).config(function ($routeProvider) { 
$routeProvider.when('/route1', { 
controller: 'Controller1 as c1', 
templateUrl: 'views/view1.html', 
}).when('/route2', { 
controller: 'Controller2 as c2', 
templateUrl: 'views/view2.html', 
}).otherwise({ 
redirectTo: '/route1' 
}); 
}); 
Route1 
Controller1 
View1 
Route2 
Controller2 
View2 
Map routes to controllers and views 
app.js 
14
angular.module('myApp', [ 
'ngRoute', 
… 
]).config(function ($routeProvider) { 
$routeProvider.when('/showProjects', { 
controller: 'ShowProjectsController as show', 
templateUrl: ‘views/showProjects.html’, 
}).when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}).otherwise({ 
redirectTo: '/login' 
}); 
}); 
Routes! 
http://../login 
http://../showProjects 
app.js 
15
angular.module('myApp', [ 
'ngRoute', 
… 
]).config(function ($routeProvider) { 
$routeProvider.when('/showProjects', { 
controller: 'ShowProjectsController as show', 
templateUrl: ‘views/showProjects.html’, 
}).when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}).otherwise({ 
redirectTo: '/login' 
}); 
}); 
Routes! 
http://../login 
http://../showProjects 
app.js 
15
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
Routes! 
http://../login 
http://../showProjects 
app.js 
16
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
Pattern 
! 
Controller in Typescript Classes! 
! 
Packages the controller logic and makes 
it much easier to read and test. 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! 
<form name="loginForm" class="navbar-form navbar-left form-signin"> 
<input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> 
<input ng-model="login.password" type="password" class="form-control" placeholder="Password" 
required> 
<button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> 
</form> 
login.html 
TypeScript 
Simple OO Code 
AngularJS 
Creates an object in app.js 
app.js 
Optional Pattern 
! 
Named Controller Objects! 
! 
Maintains modularity and avoids having 
to put data into scope objects, which 
hold so much other stuff, making it easier 
to debug. 
when('/login', { 
controller: 'LoginController as login', 
templateUrl: 'views/login.html', 
}) 
17
! 
class LoginController { 
$location: ng.ILocationService; 
password: string; 
userName: string; 
constructor($location:ng.ILocationService) { 
this.$location = $location; 
this.userName = ""; 
this.password = ""; 
} 
doLogin() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode(this.userName, this.password) 
.then((response) => this.onLoginSuccess(response)) 
} 
onLoginSuccess(response){ 
if (response.status) { 
this.$location.path("/showProjects"); 
} else { 
this.$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
! 
! function LoginController($scope, $location) { 
$scope.userName = ""; 
$scope.password = ""; 
$scope.doLogin = function() { 
// Call your login code with a call back to loginSuccessFn 
callSomeAuthCode($scope.userName, $scope.password) 
.then($scope.onLoginSuccess(response)) 
} 
$scope.onLoginSuccess = function(response){ 
if (response.status) { 
$location.path("/showProjects"); 
} else { 
$location.path("/login"); 
console.log("Login failed. You cannot proceed.") 
} 
} 
} 
!! 
Typescript provides 
more readable structure 
More funky 
All initialization is in constructor 
Code completion; No $scope 
Initialization is not clearly demarcated, 
declarations interspersed with execution 
! 
No code completion 
18 
Controller in Typescript Classes
19
19
Single-Page Application 
Rich interactions within a single page, loaded only once 
Navigation 
Workspace 
Actions 
Menu 
#smartorgdev 20
#smartorgdev 21
#smartorgdev 21
If we have only one controller and one view, 
how do we prevent our code from getting 
bloated? 
22
Directives to the rescue 
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body> 
23 
template.html 
directive.js
Directives to the rescue 
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body> 
23 
template.html 
directive.js 
Pattern: ! 
Decompose view with directives
Directives to the rescue 
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body> 
23 
template.html 
directive.js
But wait, how do we pass data between 
directives? 
Navigation 
Workspace 
Actions 
Menu 
e.g. How does the Navigation directive find out about a 
selection in the Menu directive? 
#smartorgdev 24
But wait, how do we pass data between 
directives? 
Navigation 
Workspace 
Actions 
Menu 
e.g. list of trees 
e.g. How does the Navigation directive find out which 
tree has been selected in the Menu directive? 
#smartorgdev 25
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
26
myController 
angular.module('myApp') 
.controller('myController', 
function ($scope, $route, $routeParams, $location) { 
$scope.action = 'login'; 
$scope.$on("$routeChangeSuccess", 
function( $currentRoute, $previousRoute ) { 
$scope.action = $route.current.action; 
}); 
$scope.login = function() { 
// do login, and on success: 
$location.path('/home'); 
} 
$scope.onSelectTree = function(treeID) { 
// go fetch the tree using the treeID, … 
// myTree holds the tree 
$scope.$broadcast("treeLoaded", { 
"myTree": myTree 
}); 
} 
}); 
navigation 
directive 
angular.module('myApp') 
.directive("navigation", function() { 
return { 
restrict: 'A', 
scope: { 
myTree: '=' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.$on('treeLoaded', function() { 
makeTreeWith(scope.myTree); 
//.. 
} 
} 
}); 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
But wait, how does scope.onSelectTree 
work correctly when called in the menu 
directive? 
26
index.html 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
27 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body>
index.html 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
27 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body>
index.html 
menu 
directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
27 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
<div top-menu-view …></div> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body>
<div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> 
menu directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
28 
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body>
<div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> 
menu directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
28 
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
</div> 
</div> 
</div> 
… 
</body>
index.html 
<body ng-app="myApp" ng-controller="myController"> 
<div id="homescope"> 
<div class="row"> 
<div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> 
menu directive 
angular.module('myApp') 
.directive("topMenuView", function() { 
return { 
restrict: 'A', 
scope: { 
menuData: '=', 
onSelectTree: '&' 
} 
}, 
templateUrl: '...', 
link: function(scope) { 
scope.chooseTree = function(tree){ 
scope.onSelectTree({treeID: tree.id}); 
}; 
} 
}); 
28 
</div> 
<div class="row"> 
<div id="leftPanel"> 
<div nav-widget …></div> 
</div> 
<div id="middlePanel"> 
<div workspace-contents …></div> 
</div> 
<div id="rightPanel" > 
<div action-menu …></div> 
Pattern: ! 
Pass Controller Functions as Directive 
Attributes 
</div> 
</div> 
</div> 
… 
</body>
Legacy Application 
Non-JS MVC model and cannot make direct web calls from JS 
to your application 
Generate HTML stubs that invoke “widgets” 
using directives 
! 
#smartorgdev 29
30
30
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
widget 
container 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
widget 
container 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
widget 
container 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
widget 
container 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
31
In your legacy web application, render this html: 
<div id="mywidgetOuterDiv">! 
<div id="mywidget" class="" ng-controller="MyWidgetCtrl">! 
<div mywidget-directive! 
input-data="inputData" ! 
setup-data="setupData()">! 
</div> ! 
</div>! 
</div>! 
! 
widget 
directive 
In your JS code, bootstrap your widget: 
angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! 
var scope = $('#mywidget').scope();! 
scope.setupData(APP.myWidgetConfig);! 
! 
widget 
controller 
widget 
container 
and this JS: 
jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 
Pattern: ! 
Dynamically invoke angular application 
31
angular.module('myApp')! 
.controller('MyWidgetCtrl', function($scope) {! 
$scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! 
if (!$scope.inputData && !myWidgetConfig) {! 
addTablesConfig = window.standalone.myWidgetConfig;! 
} else if ($scope.inputData && !myWidgetConfig) {! 
return;! 
}! 
$scope.inputData = myWidgetConfig.inputData;! 
};! 
})! 
.directive('mywidgetDirective', function() {! 
var templateUrl = '/path/to/template.html';! 
if (!window.production) {! 
templateUrl = 'localpath/to/template.html'! 
}! 
return {! 
restrict: 'A',! 
templateUrl: templateUrl,! 
scope: {! 
inputData: '=',! 
setupData: '&'! 
},! 
link: function (scope, elem, attrs, ctrl) {! 
}! 
}! 
});! 
! 
Data made available to 
directive scope 
standalone.js 
32
angular.module('myApp')! 
.controller('MyWidgetCtrl', function($scope) {! 
$scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! 
if (!$scope.inputData && !myWidgetConfig) {! 
addTablesConfig = window.standalone.myWidgetConfig;! 
} else if ($scope.inputData && !myWidgetConfig) {! 
return;! 
}! 
$scope.inputData = myWidgetConfig.inputData;! 
};! 
})! 
.directive('mywidgetDirective', function() {! 
var templateUrl = '/path/to/template.html';! 
if (!window.production) {! 
templateUrl = 'localpath/to/template.html'! 
}! 
return {! 
restrict: 'A',! 
templateUrl: templateUrl,! 
scope: {! 
inputData: '=',! 
setupData: '&'! 
},! 
link: function (scope, elem, attrs, ctrl) {! 
}! 
}! 
});! 
! 
Data made available to 
directive scope 
standalone.js 
32
angular.module('myApp')! 
.controller('MyWidgetCtrl', function($scope) {! 
$scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! 
if (!$scope.inputData && !myWidgetConfig) {! 
addTablesConfig = window.standalone.myWidgetConfig;! 
} else if ($scope.inputData && !myWidgetConfig) {! 
return;! 
}! 
$scope.inputData = myWidgetConfig.inputData;! 
};! 
})! 
.directive('mywidgetDirective', function() {! 
var templateUrl = '/path/to/template.html';! 
if (!window.production) {! 
templateUrl = 'localpath/to/template.html'! 
}! 
return {! 
restrict: 'A',! 
templateUrl: templateUrl,! 
scope: {! 
inputData: '=',! 
setupData: '&'! 
},! 
link: function (scope, elem, attrs, ctrl) {! 
}! 
}! 
});! 
! 
Data made available to 
directive scope 
standalone.js 
32
angular.module('myApp')! 
.controller('MyWidgetCtrl', function($scope) {! 
$scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! 
if (!$scope.inputData && !myWidgetConfig) {! 
addTablesConfig = window.standalone.myWidgetConfig;! 
} else if ($scope.inputData && !myWidgetConfig) {! 
return;! 
}! 
$scope.inputData = myWidgetConfig.inputData;! 
};! 
})! 
.directive('mywidgetDirective', function() {! 
var templateUrl = '/path/to/template.html';! 
if (!window.production) {! 
Allows for standalone 
templateUrl = 'localpath/to/template.html'! 
}! 
return {! 
restrict: 'A',! 
templateUrl: templateUrl,! 
scope: {! 
inputData: '=',! 
setupData: '&'! 
},! 
link: function (scope, elem, attrs, ctrl) {! 
}! 
}! 
});! 
! 
Data made available to 
directive scope 
standalone.js 
32 
widget testing 
window.standalone = {! 
myWidgetConfig: {! 
inputData: …! 
}! 
}! 
! 
standalone index.html
angular.module('myApp')! 
.controller('MyWidgetCtrl', function($scope) {! 
$scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! 
if (!$scope.inputData && !myWidgetConfig) {! 
addTablesConfig = window.standalone.myWidgetConfig;! 
} else if ($scope.inputData && !myWidgetConfig) {! 
return;! 
}! 
$scope.inputData = myWidgetConfig.inputData;! 
};! 
})! 
.directive('mywidgetDirective', function() {! 
var templateUrl = '/path/to/template.html';! 
if (!window.production) {! 
Allows for standalone 
templateUrl = 'localpath/to/template.html'! 
}! 
return {! 
restrict: 'A',! 
templateUrl: templateUrl,! 
scope: {! 
inputData: '=',! 
setupData: '&'! 
},! 
link: function (scope, elem, attrs, ctrl) {! 
}! 
}! 
});! 
! 
Data made available to 
directive scope 
standalone.js 
Pattern: ! 
Standalone Widget Mode 
32 
widget testing 
window.standalone = {! 
myWidgetConfig: {! 
inputData: …! 
}! 
}! 
! 
standalone index.html
Testing notes 
Avoid putting business logic in controllers 
! 
Put them in Typescript classes that can be independently 
tested 
! 
Use HttpMocks to ensure controllers work fine 
#smartorgdev 33
Old ideas like modularity, once-and-only-once, 
etc. still apply 
! 
They just look different 
#smartorgdev 34
Get expert help 
35
Join us tomorrow for a Test-Driven Development 
session 
! 
10:45 AM 
Check us out at http://rangal.com and http://smartorg.com 
#smartorgdev 36
Open House! 
! 
Pattern Summary! 
! 
Controller in Typescript classes (17) 
Named Controller objects (17) 
! 
Decompose View with Directives (22) 
! 
Pass Controller Functions as Directive 
Attributes (28) 
! 
Dynamically Invoke Angular Application (31) 
! 
Standalone Widget Mode (32) 
! 
37 
! 
Questions for reflection 
! 
What’s your AngularJS 
adoption experience? 
! 
What have you learned? 
Write to us! 
Somik Raha: sraha@smartorg.com 
Kai Wu: kwu@smartorg.com 
#smartorgdev

More Related Content

What's hot

https://www.facebook.com/valdyna.monna?fref=ts
https://www.facebook.com/valdyna.monna?fref=tshttps://www.facebook.com/valdyna.monna?fref=ts
https://www.facebook.com/valdyna.monna?fref=ts
Arif Alexi
 
Who Needs Ruby When You've Got CodeIgniter
Who Needs Ruby When You've Got CodeIgniterWho Needs Ruby When You've Got CodeIgniter
Who Needs Ruby When You've Got CodeIgniter
ciconf
 
Javascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & TricksJavascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & Tricks
Hjörtur Hilmarsson
 

What's hot (20)

Jquery In Rails
Jquery In RailsJquery In Rails
Jquery In Rails
 
https://www.facebook.com/valdyna.monna?fref=ts
https://www.facebook.com/valdyna.monna?fref=tshttps://www.facebook.com/valdyna.monna?fref=ts
https://www.facebook.com/valdyna.monna?fref=ts
 
날로 먹는 Django admin 활용
날로 먹는 Django admin 활용날로 먹는 Django admin 활용
날로 먹는 Django admin 활용
 
Play, Slick, play2-authの間で討死
Play, Slick, play2-authの間で討死Play, Slick, play2-authの間で討死
Play, Slick, play2-authの間で討死
 
Slimme Joomla! Templating Tips en Truuks
Slimme Joomla! Templating Tips en TruuksSlimme Joomla! Templating Tips en Truuks
Slimme Joomla! Templating Tips en Truuks
 
Defenders quest cheat.dfq
Defenders quest cheat.dfqDefenders quest cheat.dfq
Defenders quest cheat.dfq
 
Geb qa fest2017
Geb qa fest2017Geb qa fest2017
Geb qa fest2017
 
Who Needs Ruby When You've Got CodeIgniter
Who Needs Ruby When You've Got CodeIgniterWho Needs Ruby When You've Got CodeIgniter
Who Needs Ruby When You've Got CodeIgniter
 
Game jump: frontend introduction #1
Game jump: frontend introduction #1Game jump: frontend introduction #1
Game jump: frontend introduction #1
 
Send.php
Send.phpSend.php
Send.php
 
The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)The Web beyond "usernames & passwords" (OSDC12)
The Web beyond "usernames & passwords" (OSDC12)
 
Devoxx 2014-webComponents
Devoxx 2014-webComponentsDevoxx 2014-webComponents
Devoxx 2014-webComponents
 
Php if
Php ifPhp if
Php if
 
[ WrocLoveRb 2012] user perspective testing using ruby
[ WrocLoveRb 2012] user perspective testing using ruby[ WrocLoveRb 2012] user perspective testing using ruby
[ WrocLoveRb 2012] user perspective testing using ruby
 
Borrador del blog
Borrador del blogBorrador del blog
Borrador del blog
 
Persona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwordsPersona: in your browsers, killing your passwords
Persona: in your browsers, killing your passwords
 
Design Patterns for Tablets and Smartphones
Design Patterns for Tablets and SmartphonesDesign Patterns for Tablets and Smartphones
Design Patterns for Tablets and Smartphones
 
Symfony2. Form and Validation
Symfony2. Form and ValidationSymfony2. Form and Validation
Symfony2. Form and Validation
 
Javascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & TricksJavascript MVC & Backbone Tips & Tricks
Javascript MVC & Backbone Tips & Tricks
 
Modular and Event-Driven JavaScript
Modular and Event-Driven JavaScriptModular and Event-Driven JavaScript
Modular and Event-Driven JavaScript
 

Viewers also liked

Decision Quality Tools and Techniques
Decision Quality Tools and TechniquesDecision Quality Tools and Techniques
Decision Quality Tools and Techniques
fadinajdi
 

Viewers also liked (15)

Failing forward v2
Failing forward v2Failing forward v2
Failing forward v2
 
Innovation story
Innovation storyInnovation story
Innovation story
 
Embedded Decision Analysis: Systems Design Patterns
Embedded Decision Analysis: Systems Design PatternsEmbedded Decision Analysis: Systems Design Patterns
Embedded Decision Analysis: Systems Design Patterns
 
Principles of strategic portfolio management
Principles of strategic portfolio managementPrinciples of strategic portfolio management
Principles of strategic portfolio management
 
Embedded DA vs Consultative DA: Audience Workshop
Embedded DA vs Consultative DA: Audience WorkshopEmbedded DA vs Consultative DA: Audience Workshop
Embedded DA vs Consultative DA: Audience Workshop
 
Agile business development
Agile business developmentAgile business development
Agile business development
 
Portfolio management 101
Portfolio management 101Portfolio management 101
Portfolio management 101
 
Framing Patterns
Framing PatternsFraming Patterns
Framing Patterns
 
Decision Quality Tools and Techniques
Decision Quality Tools and TechniquesDecision Quality Tools and Techniques
Decision Quality Tools and Techniques
 
Head and Heart in Decision Quality
Head and Heart in Decision QualityHead and Heart in Decision Quality
Head and Heart in Decision Quality
 
Experimenting with Values
Experimenting with ValuesExperimenting with Values
Experimenting with Values
 
Stanford-SDG Webinar Six critical principles of strategic portfolio management
Stanford-SDG Webinar Six critical principles of strategic portfolio managementStanford-SDG Webinar Six critical principles of strategic portfolio management
Stanford-SDG Webinar Six critical principles of strategic portfolio management
 
Embedded Decision Analysis
Embedded Decision AnalysisEmbedded Decision Analysis
Embedded Decision Analysis
 
Accelerating the deployment of technology to business opportunities-chevron T...
Accelerating the deployment of technology to business opportunities-chevron T...Accelerating the deployment of technology to business opportunities-chevron T...
Accelerating the deployment of technology to business opportunities-chevron T...
 
Agile business development
Agile business developmentAgile business development
Agile business development
 

Similar to Design strategies for AngularJS

Private slideshow
Private slideshowPrivate slideshow
Private slideshow
sblackman
 
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
Nico Miceli
 

Similar to Design strategies for AngularJS (20)

Angular Workshop_Sarajevo2
Angular Workshop_Sarajevo2Angular Workshop_Sarajevo2
Angular Workshop_Sarajevo2
 
Clean Javascript
Clean JavascriptClean Javascript
Clean Javascript
 
Private slideshow
Private slideshowPrivate slideshow
Private slideshow
 
Building an End-to-End AngularJS Application
Building an End-to-End AngularJS ApplicationBuilding an End-to-End AngularJS Application
Building an End-to-End AngularJS Application
 
Advanced jQuery
Advanced jQueryAdvanced jQuery
Advanced jQuery
 
Mobile Open Day: React Native: Crossplatform fast dive
Mobile Open Day: React Native: Crossplatform fast diveMobile Open Day: React Native: Crossplatform fast dive
Mobile Open Day: React Native: Crossplatform fast dive
 
[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern
 
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
 
Oracle APEX migration to 5.1 - Our experience
Oracle APEX migration to 5.1 - Our experienceOracle APEX migration to 5.1 - Our experience
Oracle APEX migration to 5.1 - Our experience
 
Functional Reactive Programming - RxSwift
Functional Reactive Programming - RxSwiftFunctional Reactive Programming - RxSwift
Functional Reactive Programming - RxSwift
 
The Art of AngularJS in 2015 - Angular Summit 2015
The Art of AngularJS in 2015 - Angular Summit 2015The Art of AngularJS in 2015 - Angular Summit 2015
The Art of AngularJS in 2015 - Angular Summit 2015
 
Mashing up JavaScript
Mashing up JavaScriptMashing up JavaScript
Mashing up JavaScript
 
ui-router and $state
ui-router and $stateui-router and $state
ui-router and $state
 
Mashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web AppsMashing up JavaScript – Advanced Techniques for modern Web Apps
Mashing up JavaScript – Advanced Techniques for modern Web Apps
 
Angular js routing options
Angular js routing optionsAngular js routing options
Angular js routing options
 
Angular js - 4developers 12 kwietnia 2013
Angular js - 4developers 12 kwietnia 2013Angular js - 4developers 12 kwietnia 2013
Angular js - 4developers 12 kwietnia 2013
 
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
Some Advanced Tracking in Google Analytics in 5 mins - PhillyJS meet up
 
ReactJs presentation
ReactJs presentationReactJs presentation
ReactJs presentation
 
DrupalCon Dublin 2016 - Automated browser testing with Nightwatch.js
DrupalCon Dublin 2016 - Automated browser testing with Nightwatch.jsDrupalCon Dublin 2016 - Automated browser testing with Nightwatch.js
DrupalCon Dublin 2016 - Automated browser testing with Nightwatch.js
 
Ditching JQuery
Ditching JQueryDitching JQuery
Ditching JQuery
 

Recently uploaded

Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdf
ciinovamais
 
1029-Danh muc Sach Giao Khoa khoi 6.pdf
1029-Danh muc Sach Giao Khoa khoi  6.pdf1029-Danh muc Sach Giao Khoa khoi  6.pdf
1029-Danh muc Sach Giao Khoa khoi 6.pdf
QucHHunhnh
 
Spellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please PractiseSpellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please Practise
AnaAcapella
 
The basics of sentences session 3pptx.pptx
The basics of sentences session 3pptx.pptxThe basics of sentences session 3pptx.pptx
The basics of sentences session 3pptx.pptx
heathfieldcps1
 
Vishram Singh - Textbook of Anatomy Upper Limb and Thorax.. Volume 1 (1).pdf
Vishram Singh - Textbook of Anatomy  Upper Limb and Thorax.. Volume 1 (1).pdfVishram Singh - Textbook of Anatomy  Upper Limb and Thorax.. Volume 1 (1).pdf
Vishram Singh - Textbook of Anatomy Upper Limb and Thorax.. Volume 1 (1).pdf
ssuserdda66b
 
Salient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functionsSalient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functions
KarakKing
 

Recently uploaded (20)

Activity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdfActivity 01 - Artificial Culture (1).pdf
Activity 01 - Artificial Culture (1).pdf
 
1029-Danh muc Sach Giao Khoa khoi 6.pdf
1029-Danh muc Sach Giao Khoa khoi  6.pdf1029-Danh muc Sach Giao Khoa khoi  6.pdf
1029-Danh muc Sach Giao Khoa khoi 6.pdf
 
ComPTIA Overview | Comptia Security+ Book SY0-701
ComPTIA Overview | Comptia Security+ Book SY0-701ComPTIA Overview | Comptia Security+ Book SY0-701
ComPTIA Overview | Comptia Security+ Book SY0-701
 
General Principles of Intellectual Property: Concepts of Intellectual Proper...
General Principles of Intellectual Property: Concepts of Intellectual  Proper...General Principles of Intellectual Property: Concepts of Intellectual  Proper...
General Principles of Intellectual Property: Concepts of Intellectual Proper...
 
Dyslexia AI Workshop for Slideshare.pptx
Dyslexia AI Workshop for Slideshare.pptxDyslexia AI Workshop for Slideshare.pptx
Dyslexia AI Workshop for Slideshare.pptx
 
Key note speaker Neum_Admir Softic_ENG.pdf
Key note speaker Neum_Admir Softic_ENG.pdfKey note speaker Neum_Admir Softic_ENG.pdf
Key note speaker Neum_Admir Softic_ENG.pdf
 
Sociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning ExhibitSociology 101 Demonstration of Learning Exhibit
Sociology 101 Demonstration of Learning Exhibit
 
Micro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdfMicro-Scholarship, What it is, How can it help me.pdf
Micro-Scholarship, What it is, How can it help me.pdf
 
On National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan FellowsOn National Teacher Day, meet the 2024-25 Kenan Fellows
On National Teacher Day, meet the 2024-25 Kenan Fellows
 
SOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning PresentationSOC 101 Demonstration of Learning Presentation
SOC 101 Demonstration of Learning Presentation
 
Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...Making communications land - Are they received and understood as intended? we...
Making communications land - Are they received and understood as intended? we...
 
Spellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please PractiseSpellings Wk 3 English CAPS CARES Please Practise
Spellings Wk 3 English CAPS CARES Please Practise
 
Fostering Friendships - Enhancing Social Bonds in the Classroom
Fostering Friendships - Enhancing Social Bonds  in the ClassroomFostering Friendships - Enhancing Social Bonds  in the Classroom
Fostering Friendships - Enhancing Social Bonds in the Classroom
 
FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024FSB Advising Checklist - Orientation 2024
FSB Advising Checklist - Orientation 2024
 
The basics of sentences session 3pptx.pptx
The basics of sentences session 3pptx.pptxThe basics of sentences session 3pptx.pptx
The basics of sentences session 3pptx.pptx
 
Holdier Curriculum Vitae (April 2024).pdf
Holdier Curriculum Vitae (April 2024).pdfHoldier Curriculum Vitae (April 2024).pdf
Holdier Curriculum Vitae (April 2024).pdf
 
HMCS Max Bernays Pre-Deployment Brief (May 2024).pptx
HMCS Max Bernays Pre-Deployment Brief (May 2024).pptxHMCS Max Bernays Pre-Deployment Brief (May 2024).pptx
HMCS Max Bernays Pre-Deployment Brief (May 2024).pptx
 
Vishram Singh - Textbook of Anatomy Upper Limb and Thorax.. Volume 1 (1).pdf
Vishram Singh - Textbook of Anatomy  Upper Limb and Thorax.. Volume 1 (1).pdfVishram Singh - Textbook of Anatomy  Upper Limb and Thorax.. Volume 1 (1).pdf
Vishram Singh - Textbook of Anatomy Upper Limb and Thorax.. Volume 1 (1).pdf
 
Salient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functionsSalient Features of India constitution especially power and functions
Salient Features of India constitution especially power and functions
 
ICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptxICT Role in 21st Century Education & its Challenges.pptx
ICT Role in 21st Century Education & its Challenges.pptx
 

Design strategies for AngularJS

  • 2. Design Strategies with AngularJS 1 Somik Raha
  • 3. Design Strategies with AngularJS Somik Raha Kai Wu 1
  • 4. Design Strategies with AngularJS Somik Raha Kai Wu 1 #smartorgdev
  • 5. Rip Van Winkle Slept through the American Revolution Slept off in 2004 and woke up in 2014 2
  • 6. 3
  • 11. 2004… C++ Java Microsoft 3
  • 12. 2004… C++ Java Microsoft Sun 3
  • 13. 2004… C++ Java Microsoft Sun #smartorgdev 3
  • 14. 2014. C++ Java Microsoft Sun #smartorgdev 4
  • 23. First reaction I don’t get it. Backbone is great for us. #smartorgdev 8
  • 24. Second reaction Whoa!! #smartorgdev 9
  • 25. Backbone learning curve AngularJS learning curve Flattens quickly Minimal concepts 10 Service Directives Controllers and views Staircase … !
  • 26. Backbone learning curve AngularJS learning curve Flattens quickly Minimal concepts 10 Ben Nadel’s blog
  • 27. Stats comparing Backbone with AngularJS AppStructure Wizard #smartorgdev 11
  • 28. Stats comparing Backbone with AngularJS AppStructure Wizard #smartorgdev 11
  • 29. Stats comparing Backbone with AngularJS AppStructure Wizard #smartorgdev 11
  • 30. Three Kinds of Scenarios Single Page Application Multi-Page Application Legacy Application Classic Sophisticated Convoluted #smartorgdev 12
  • 31. Multi-Page Application Browser loads entire page e.g. Login e.g. Show Projects Page 1 Page 2 Page 3 View 1 View 2 View 3 Controller 1 Controller 2 Controller 3 Route 1 Route 2 Route 3 /login /showProjects /… Map routes to controllers and views #smartorgdev 13
  • 32. angular.module('myApp', [ 'ngRoute', … ]).config(function ($routeProvider) { $routeProvider.when('/route1', { controller: 'Controller1 as c1', templateUrl: 'views/view1.html', }).when('/route2', { controller: 'Controller2 as c2', templateUrl: 'views/view2.html', }).otherwise({ redirectTo: '/route1' }); }); Route1 Controller1 View1 Route2 Controller2 View2 Map routes to controllers and views app.js 14
  • 33. angular.module('myApp', [ 'ngRoute', … ]).config(function ($routeProvider) { $routeProvider.when('/showProjects', { controller: 'ShowProjectsController as show', templateUrl: ‘views/showProjects.html’, }).when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }).otherwise({ redirectTo: '/login' }); }); Routes! http://../login http://../showProjects app.js 15
  • 34. angular.module('myApp', [ 'ngRoute', … ]).config(function ($routeProvider) { $routeProvider.when('/showProjects', { controller: 'ShowProjectsController as show', templateUrl: ‘views/showProjects.html’, }).when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }).otherwise({ redirectTo: '/login' }); }); Routes! http://../login http://../showProjects app.js 15
  • 35. when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) Routes! http://../login http://../showProjects app.js 16
  • 36. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 37. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js Pattern ! Controller in Typescript Classes! ! Packages the controller logic and makes it much easier to read and test. when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 38. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 39. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 40. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 41. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 42. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! <form name="loginForm" class="navbar-form navbar-left form-signin"> <input ng-model="login.userName" class="form-control" placeholder="User Name" required autofocus> <input ng-model="login.password" type="password" class="form-control" placeholder="Password" required> <button ng-click="login.doLogin()" class="btn btn-primary" type="submit">Sign in</button> </form> login.html TypeScript Simple OO Code AngularJS Creates an object in app.js app.js Optional Pattern ! Named Controller Objects! ! Maintains modularity and avoids having to put data into scope objects, which hold so much other stuff, making it easier to debug. when('/login', { controller: 'LoginController as login', templateUrl: 'views/login.html', }) 17
  • 43. ! class LoginController { $location: ng.ILocationService; password: string; userName: string; constructor($location:ng.ILocationService) { this.$location = $location; this.userName = ""; this.password = ""; } doLogin() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode(this.userName, this.password) .then((response) => this.onLoginSuccess(response)) } onLoginSuccess(response){ if (response.status) { this.$location.path("/showProjects"); } else { this.$location.path("/login"); console.log("Login failed. You cannot proceed.") } } } ! ! function LoginController($scope, $location) { $scope.userName = ""; $scope.password = ""; $scope.doLogin = function() { // Call your login code with a call back to loginSuccessFn callSomeAuthCode($scope.userName, $scope.password) .then($scope.onLoginSuccess(response)) } $scope.onLoginSuccess = function(response){ if (response.status) { $location.path("/showProjects"); } else { $location.path("/login"); console.log("Login failed. You cannot proceed.") } } } !! Typescript provides more readable structure More funky All initialization is in constructor Code completion; No $scope Initialization is not clearly demarcated, declarations interspersed with execution ! No code completion 18 Controller in Typescript Classes
  • 44. 19
  • 45. 19
  • 46. Single-Page Application Rich interactions within a single page, loaded only once Navigation Workspace Actions Menu #smartorgdev 20
  • 49. If we have only one controller and one view, how do we prevent our code from getting bloated? 22
  • 50. Directives to the rescue index.html <body ng-app="myApp" ng-controller="myController"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body> 23 template.html directive.js
  • 51. Directives to the rescue index.html <body ng-app="myApp" ng-controller="myController"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body> 23 template.html directive.js Pattern: ! Decompose view with directives
  • 52. Directives to the rescue index.html <body ng-app="myApp" ng-controller="myController"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body> 23 template.html directive.js
  • 53. But wait, how do we pass data between directives? Navigation Workspace Actions Menu e.g. How does the Navigation directive find out about a selection in the Menu directive? #smartorgdev 24
  • 54. But wait, how do we pass data between directives? Navigation Workspace Actions Menu e.g. list of trees e.g. How does the Navigation directive find out which tree has been selected in the Menu directive? #smartorgdev 25
  • 55. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 56. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 57. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 58. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 59. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 60. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 26
  • 61. myController angular.module('myApp') .controller('myController', function ($scope, $route, $routeParams, $location) { $scope.action = 'login'; $scope.$on("$routeChangeSuccess", function( $currentRoute, $previousRoute ) { $scope.action = $route.current.action; }); $scope.login = function() { // do login, and on success: $location.path('/home'); } $scope.onSelectTree = function(treeID) { // go fetch the tree using the treeID, … // myTree holds the tree $scope.$broadcast("treeLoaded", { "myTree": myTree }); } }); navigation directive angular.module('myApp') .directive("navigation", function() { return { restrict: 'A', scope: { myTree: '=' } }, templateUrl: '...', link: function(scope) { scope.$on('treeLoaded', function() { makeTreeWith(scope.myTree); //.. } } }); menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); But wait, how does scope.onSelectTree work correctly when called in the menu directive? 26
  • 62. index.html menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 27 <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body>
  • 63. index.html menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 27 <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body>
  • 64. index.html menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 27 <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> <div top-menu-view …></div> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body>
  • 65. <div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 28 index.html <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body>
  • 66. <div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 28 index.html <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> </div> </div> </div> … </body>
  • 67. index.html <body ng-app="myApp" ng-controller="myController"> <div id="homescope"> <div class="row"> <div top-menu-view menu-data="menuData" on-select-tree="onSelectTree(treeID)"></div> menu directive angular.module('myApp') .directive("topMenuView", function() { return { restrict: 'A', scope: { menuData: '=', onSelectTree: '&' } }, templateUrl: '...', link: function(scope) { scope.chooseTree = function(tree){ scope.onSelectTree({treeID: tree.id}); }; } }); 28 </div> <div class="row"> <div id="leftPanel"> <div nav-widget …></div> </div> <div id="middlePanel"> <div workspace-contents …></div> </div> <div id="rightPanel" > <div action-menu …></div> Pattern: ! Pass Controller Functions as Directive Attributes </div> </div> </div> … </body>
  • 68. Legacy Application Non-JS MVC model and cannot make direct web calls from JS to your application Generate HTML stubs that invoke “widgets” using directives ! #smartorgdev 29
  • 69. 30
  • 70. 30
  • 71. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! 31
  • 72. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! 31
  • 73. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 31
  • 74. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller widget container and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 31
  • 75. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller widget container and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 31
  • 76. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller widget container and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 31
  • 77. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller widget container and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! 31
  • 78. In your legacy web application, render this html: <div id="mywidgetOuterDiv">! <div id="mywidget" class="" ng-controller="MyWidgetCtrl">! <div mywidget-directive! input-data="inputData" ! setup-data="setupData()">! </div> ! </div>! </div>! ! widget directive In your JS code, bootstrap your widget: angular.bootstrap($('#mywidgetOuterDiv'), ['myApp']);! var scope = $('#mywidget').scope();! scope.setupData(APP.myWidgetConfig);! ! widget controller widget container and this JS: jsCode = "APP.myWidgetConfig = { inputData: {…} }"! Pattern: ! Dynamically invoke angular application 31
  • 79. angular.module('myApp')! .controller('MyWidgetCtrl', function($scope) {! $scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! if (!$scope.inputData && !myWidgetConfig) {! addTablesConfig = window.standalone.myWidgetConfig;! } else if ($scope.inputData && !myWidgetConfig) {! return;! }! $scope.inputData = myWidgetConfig.inputData;! };! })! .directive('mywidgetDirective', function() {! var templateUrl = '/path/to/template.html';! if (!window.production) {! templateUrl = 'localpath/to/template.html'! }! return {! restrict: 'A',! templateUrl: templateUrl,! scope: {! inputData: '=',! setupData: '&'! },! link: function (scope, elem, attrs, ctrl) {! }! }! });! ! Data made available to directive scope standalone.js 32
  • 80. angular.module('myApp')! .controller('MyWidgetCtrl', function($scope) {! $scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! if (!$scope.inputData && !myWidgetConfig) {! addTablesConfig = window.standalone.myWidgetConfig;! } else if ($scope.inputData && !myWidgetConfig) {! return;! }! $scope.inputData = myWidgetConfig.inputData;! };! })! .directive('mywidgetDirective', function() {! var templateUrl = '/path/to/template.html';! if (!window.production) {! templateUrl = 'localpath/to/template.html'! }! return {! restrict: 'A',! templateUrl: templateUrl,! scope: {! inputData: '=',! setupData: '&'! },! link: function (scope, elem, attrs, ctrl) {! }! }! });! ! Data made available to directive scope standalone.js 32
  • 81. angular.module('myApp')! .controller('MyWidgetCtrl', function($scope) {! $scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! if (!$scope.inputData && !myWidgetConfig) {! addTablesConfig = window.standalone.myWidgetConfig;! } else if ($scope.inputData && !myWidgetConfig) {! return;! }! $scope.inputData = myWidgetConfig.inputData;! };! })! .directive('mywidgetDirective', function() {! var templateUrl = '/path/to/template.html';! if (!window.production) {! templateUrl = 'localpath/to/template.html'! }! return {! restrict: 'A',! templateUrl: templateUrl,! scope: {! inputData: '=',! setupData: '&'! },! link: function (scope, elem, attrs, ctrl) {! }! }! });! ! Data made available to directive scope standalone.js 32
  • 82. angular.module('myApp')! .controller('MyWidgetCtrl', function($scope) {! $scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! if (!$scope.inputData && !myWidgetConfig) {! addTablesConfig = window.standalone.myWidgetConfig;! } else if ($scope.inputData && !myWidgetConfig) {! return;! }! $scope.inputData = myWidgetConfig.inputData;! };! })! .directive('mywidgetDirective', function() {! var templateUrl = '/path/to/template.html';! if (!window.production) {! Allows for standalone templateUrl = 'localpath/to/template.html'! }! return {! restrict: 'A',! templateUrl: templateUrl,! scope: {! inputData: '=',! setupData: '&'! },! link: function (scope, elem, attrs, ctrl) {! }! }! });! ! Data made available to directive scope standalone.js 32 widget testing window.standalone = {! myWidgetConfig: {! inputData: …! }! }! ! standalone index.html
  • 83. angular.module('myApp')! .controller('MyWidgetCtrl', function($scope) {! $scope.setupData = function(myWidgetConfig:MyWidgetConfig) {! if (!$scope.inputData && !myWidgetConfig) {! addTablesConfig = window.standalone.myWidgetConfig;! } else if ($scope.inputData && !myWidgetConfig) {! return;! }! $scope.inputData = myWidgetConfig.inputData;! };! })! .directive('mywidgetDirective', function() {! var templateUrl = '/path/to/template.html';! if (!window.production) {! Allows for standalone templateUrl = 'localpath/to/template.html'! }! return {! restrict: 'A',! templateUrl: templateUrl,! scope: {! inputData: '=',! setupData: '&'! },! link: function (scope, elem, attrs, ctrl) {! }! }! });! ! Data made available to directive scope standalone.js Pattern: ! Standalone Widget Mode 32 widget testing window.standalone = {! myWidgetConfig: {! inputData: …! }! }! ! standalone index.html
  • 84. Testing notes Avoid putting business logic in controllers ! Put them in Typescript classes that can be independently tested ! Use HttpMocks to ensure controllers work fine #smartorgdev 33
  • 85. Old ideas like modularity, once-and-only-once, etc. still apply ! They just look different #smartorgdev 34
  • 87. Join us tomorrow for a Test-Driven Development session ! 10:45 AM Check us out at http://rangal.com and http://smartorg.com #smartorgdev 36
  • 88. Open House! ! Pattern Summary! ! Controller in Typescript classes (17) Named Controller objects (17) ! Decompose View with Directives (22) ! Pass Controller Functions as Directive Attributes (28) ! Dynamically Invoke Angular Application (31) ! Standalone Widget Mode (32) ! 37 ! Questions for reflection ! What’s your AngularJS adoption experience? ! What have you learned? Write to us! Somik Raha: sraha@smartorg.com Kai Wu: kwu@smartorg.com #smartorgdev