The FortiOS way
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
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'])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.js provides multiple utilities to facilitate lazy loading:
loaderProvider
Configuration time methods
Saves other providers to use later
.resolve(['module', ...])
/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(...)// 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);
}
});Old way:
The controller instance becomes a powerful interface, instead of a heap of variable assignments.
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!
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');
};
}Transclusion allows us to create very customizable components.
Provides a mechanism to wrap a template fragment inside a component
The new <f-dialog> directive uses transclusion to wrap it's content with dialog html
<a-slot>Angular JS 1.5</a-slot>
<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>
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!
}Solution: Custom 'decorator'.
Solution: Custom 'decorator'.
✗
✗
AngularJS has excellent support for:
$resource and $http provide:
CMDB service provides:
CMDB data:
Adding behavior to models promotes:
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 }">$routeConfig: [
{ path: '/heroes/...', component: 'someComponent' },
...
]
...
.component('heroes', {
$routeConfig: [
{path: '/', name: 'HeroList',
component: 'heroList', useAsDefault: true},
{path: '/:id', name: 'HeroDetail',
component: 'heroDetail'}
]
})