Angular JS
The FortiOS way

Routes
Components
models



Routes
Routes distinguish sections of a Single Page App

$routeProvider
.when('/log/view/:logType', {
template: '<f-log-view></f-log-view>',
css: [
'main.css',
'/ng/directives/faceted_search/faceted_search.css'
],
resolve: loaderProvider.resolve(['f-log-view'])
})
.when(...)
.when(...);http://fortigate/ng/log/view/app => $routeParams: {logType: 'app'}
http://fortigate/ng/log/view/ips => $routeParams: {logType: 'ips'}
http://fortigate/ng/log/view/av => $routeParams: {logType: 'av'}
http://fortigate/ng/log/view/traffic => $routeParams: {logType: 'traffic'}These urls will load the <f-log-view> component and fill in the $routeParams Service
Route Configuration
Use an inline template to load the main route component
template: '<f-log-view></f-log-view>'Add stylus the output from files using the custom css property
css: [
'main.css',
'/ng/directives/faceted_search/faceted_search.css'
]Use loaderProvider to resolve the component's lazy module.
// only load one requirejs module
resolve: loaderProvider.resolve(['f-log-view'])

Lazy Loading

Lazy Loading

-
preLoader aka require('loader')
-
.preloadModules(['module', ...])
-
Used by $.ng_app()
-
Requires all modules recursively, so they can be instantiated synchronously when the app runs.
-
.initModules(['module', ...], currentModule)
-
.base_path('template.html', currentModule)
-
.cache_path() .route_path()
DEPRECATED!
-
Run time loader methods
- loader aka LoaderService
-
loader.js provides multiple utilities to facilitate lazy loading:
-
loaderProvider
-
Configuration time methods
-
Saves other providers to use later
-
.resolve(['module', ...])
Lazy Modules

Lazy Modules
/ng/log/view/:logType
/ng/log/view/traffic
loaderProvider.resolve(['f-log-view'])loader.initModules(['f-log-view'])f-log-view.js
return function register(providers, loader) {
providers.$compile.component({
fLogView: fLogView
});
return loader.initModules([
'data',
'formatters',
'f-log-view-menu',
'f-log-details',
'/ng/directives/menu/templates'
], module);
};providers.$compile.component(...)loader.initModules([...], module)data.js
formatters.js
f-log-view-menu.js
f-log-details.js
/ng/directives/menu/templates.js
Call loader.initModules() inside the register() function to declutter app.js

data.js
return function(providers, loader) {
providers.$compile.component({
fLogDetails: fLogDetails
});
return loader.initModules([
'f-log-detail-section',
'data',
'/ng/directives/virtual_scroll'
], module);
};providers.$compile.component(...)f-log-details.js
loader.initModules([...], module)f-log-detail-section.js
/ng/directives/virtual_scroll.js
return function(providers, loader) {
providers.$compile.component({
fLogDetailSection: fLogDetailSection
});
return loader.initModules([
'f-log-detail-datum'
], module);
};providers.$compile.component(...)f-log-detail-datum.js
return function(providers) {
providers.$compile.component({
fLogDetailDatum: fLogDetailDatum
});
};loader.initModules([...], module)providers.$compile.component(...)Components

// new 'template directive' best practice:
.directive('f-log-menu', function fLogViewMenuFactory() {
return {
restrict: 'E',
scope: {
menu: '=',
logView: '='
},
bindToController: true,
controllerAs: 'logViewMenu',
controller: LogViewMenu,
templateUrl: loader.base_path('menu.html', module)
};
});// component uses best practices by default!
.component('counter', {
bindings: {
menu: '=',
logView: '='
},
controllerAs: 'logViewMenu', // default '$ctrl'
controller: LogViewMenu,
templateUrl: function(loader) {
return loader.base_path('menu.html', module);
}
});crafting an interface
Old way:








- ng-controller="SomeController"
- Spray variables into $scope
- ng-template="some-template.html"
- Scoop variables out of $scope
CONTROLLERAS
(And bindTocontroller)

The controller instance becomes a powerful interface, instead of a heap of variable assignments.
enter:
- Properties related to each controller are isolated to the owning controller
- $scope contains only controllers
- Component inputs and outputs are declared
- Developers know where to look to find the declaration (searchable)
Important notes
-
Components aren't replacements for directives. A component is a special type of directive that organizes a controller with a template.
-
Components do not have a link function and controllers still are not where you'd handle DOM manipulation.
-
If you need DOM manipulation, your component can use other directives that include that DOM manipulation in a link function.
-
Components are directives but not all directives need to be components!

stateless components
not every component needs a controller.

What if I told you:
var fLogDetailDatum{
bindings: {
/**
* @type {Datum[]}
*/
datum: '>',
/**
* Array of language string prefixes to try in order when translating datum names.
* @type {String[]}
*/
namePrefixes: '>'
},
template: [
'<td class="datum-name" data-name="{{ datum.name }}">',
' <f-icon class="{{:: $ctrl.datum.icon }}" ng-if="$ctrl.datum.icon"></f-icon>',
' <span ng-if="datum.name">{{:: $ctrl.datum.name | langPrefixed:$ctrl.namePrefixes }}</span>',
'</td>',
'<td class="datum-value">',
' <f-icon class="{{ $ctrl.datum.valueIcon }}" ng-if="$ctrl.datum.valueIcon"></f-icon>',
' <span ng-bind-html="$ctrl.datum.getValue ? $ctrl.datum.getValue() : datum.value"></span>',
'</td>'
].join('\n');
};
} var fLogDetailDatum{
bindings: {
/**
* @type {Datum[]}
*/
datum: '>',
/**
* Array of language string prefixes to try in order when translating datum names.
* @type {String[]}
*/
namePrefixes: '>'
},
template: [
'<td class="datum-name" data-name="{{ datum.name }}">',
' <f-icon class="{{:: $ctrl.datum.icon }}" ng-if="$ctrl.datum.icon"></f-icon>',
' <span ng-if="datum.name">{{:: $ctrl.datum.name | langPrefixed:$ctrl.namePrefixes }}</span>',
'</td>',
'<td class="datum-value">',
' <f-icon class="{{ $ctrl.datum.valueIcon }}" ng-if="$ctrl.datum.valueIcon"></f-icon>',
' <span ng-bind-html="$ctrl.datum.getValue ? $ctrl.datum.getValue() : datum.value"></span>',
'</td>'
].join('\n');
};
}
clu
Transclusion allows us to create very customizable components.
Trans
sion
Provides a mechanism to wrap a template fragment inside a component
clu
The new <f-dialog> directive uses transclusion to wrap it's content with dialog html
Trans
sion
clA
CLB
clC
<a-slot>Angular JS 1.5</a-slot>
Trans
sion
<b-slot>Allows multiple transclusion slots</b-slot>
<c-slot>For more reuseable components</c-slot>
<ng-transclude ng-transclude-slot="a-slot">
Angular JS 1.5
</ng-transclude>
<ng-transclude ng-transclude-slot="b-slot">
Allows multiple transclusion slots
</ng-transclude>
<ng-transclude ng-transclude-slot="c-slot">
For more reuseable components
</ng-transclude>


DI Binding
AngularJS leverages Dependency Injection heavily. But it can result in massive constructor functions
function MyController($scope, greeter) {
this.sayHello = function() {
greeter.greet('Hello World');
};
// add 20 other methods
}function MyController($scope, greeter) {
this._greeter = greeter;
}
MyController.prototype = {
sayHello: function() {
this._greeter.greet('Hello World');
}
// 12 other methods that need greeter, $scope, etc
}It's better for efficiency and readability to place instance methods on the prototype.
function MyController($scope, injector) {
injector.injectMarked(this, {$scope: $scope});
}
MyController.prototype = {
sayHello: inject.mark(function sayHello(greeter) {
greeter.greet('Hello World');
}),
// 12 other methods that manage
// their own dependency injection!
}
Maintenence Nightmare
Doesn't Scale
UNREADABLE
INEFFICIENT
Solution: Custom 'decorator'.
Solution: Custom 'decorator'.
- ng/services/injector provides inject rjs module with the mark decorator.
- Wrap methods with inject.mark()
- Call injector.injectMarked(this, locals) in the constructor
- Be sure to supply a named function expression to inject.mark() so that code editors can find it.
✓
✗
✗
models
Model View Controller
AngularJS has excellent support for:


- View (templates)
- Controller
- Model?
?
$resource and $http provide:
POJO
- Plain
- Old
- Javascript
- Object
{}
Getting the OO back
CMDB service provides:
- $resource-like interface
- CMDB.Model to add prototype based on CMDB path + name
- Reset after $routeChange event
CMDB data:
- Raw values
- Odd layout
- Often doesn't match GUI requirements
Adding behavior to models promotes:
- Loose coupling
- Lean controllers
- Encapsulation
- Separation of concerns
Enhancing A Model
define(['./models', 'ngModule'],
function(models, ngModule) {
function VPNEdit(CMDB) {
CMDB.model(models);
this.phase1 = new CMDB('vpn.ipsec', 'phase')
.get(mkey);
}
ngModule.component('f-vpn-edit', {
controllerAs: 'vpnEdit',
controller: VPNEdit
});
});define(function() {
var p1Prototype = {
valueGetterSetter: function(value) {
if (arguments.length > 0) {
this._value = value;
this.updateState();
}
return this.value;
},
_updateState: function(value) {
this._state.value = value;
}
};
// note that promise will be resolved after all CMDB rows have been fetched.
function enhance(value, promise) {
// it is not necessary to wait for promise in most situations.
value._state = {};
promise.then(function() {
value._updateState(Math.random());
});
}
// add additional 'static' properties to the schema.
function enhanceSchema(schema) {
schema.children.value.$options = schema.children.value.options.filter(notBad);
function notBad(option) { return option.name === 'bad' }
}
// return an array of CMDB.Model objects for specific path/name combinations
return function(CMDB) {
return [new CMDB.Model('vpn.ipsec', 'phase1', p1Prototype, enhance, enhanceSchema)];
};
});<input type="text"
ng-model="vpnEdit.phase1.valueGetterSetter"
ng-model-options="{getterSetter: true }">AngularJS 2
- Still has directives
- @Component() decorator
- styles bound to component!
- @Directive() decorator
- TypeScript! (optional)

$routeConfig: [
{ path: '/heroes/...', component: 'someComponent' },
...
]
...
.component('heroes', {
$routeConfig: [
{path: '/', name: 'HeroList',
component: 'heroList', useAsDefault: true},
{path: '/:id', name: 'HeroDetail',
component: 'heroDetail'}
]
})
Gradual transition roadmap
- Components
- Similar to angular 2 components
-
Component Router
- Available in angular 1.5!
- provides routing on a component level for nested views
Angular 1.5
By Jamie Pate
Angular 1.5
- 1,560