在本文中我们将探索ng-view指令内部的实现方式,并且创建一个“ngMultiView”指令,
从AngularJS 1.2开始,ngView指令以及$route service 都被移动到了一个单独的ngRoute模块中。于是,如果你需要使用ngView和route的话,必须显式的声明这个模块作为依赖。另外,otherwise语法也和之前的AngularJS版本有所不同。下面就是一个route的例子,它创建了两个路由和一个默认选项:
var app = angular.module('ngViewExampleApp', ['ngRoute']);
app.controller('RootCtrl', ['$scope', function($scope){
$scope.title = "Home Page";
}]);
app.controller('CatsCtrl', ['$scope', function($scope){
$scope.title = "Cats Page";
}]);
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/', {
controller : 'RootCtrl',
template : '{{title}}
'
})
.when('/cats', {
controller : 'CatsCtrl',
template : '{{title}}
'
})
.otherwise({
redirectTo : '/'
});
}]);
在我们深入探索ngView背后的代码去之前,我们先要来说说两个文档中不存在的属性:‘onload’和’autoscroll‘。onload属性将会接收任何AngularJS表达式并在视图发生变化时执行。AutoScroll使用$autoScroll service并且基于$location.hash()的当前值滚动到一个特定的元素。最后,在指令的最后,link(currentScope)之后’$viewContentLoaded’时间将会在当前的作用域内被发射 – 你可以在你的控制器中使用这个时间。下面的代码是上面的例子的一个修改版本,其中包括一个onload属性。
var app = angular.module('ngViewExampleApp', ['ngRoute']);
app.controller('RootCtrl', ['$scope', function($scope){
$scope.title = "Home Page";
}]);
app.controller('CatsCtrl', ['$scope', function($scope){
$scope.title = "Cats Page";
}]);
app.controller('AppCtrl', ['$scope', function($scope){
$scope.onViewLoad = function(){
console.log('view changed');
};
}]);
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/', {
controller : 'RootCtrl',
template : '{{title}}
'
})
.when('/cats', {
controller : 'CatsCtrl',
template : '{{title}}
'
})
.otherwise({
redirectTo : '/'
});
}]);
为了理解ngView,我们现在来创建一个ngView的简化版本。下面是ngView的简化版本ngViewLite,它并补办扩作用域清除或者动画,除此之外和ngView基本上没有什么区别。
var app = angular.module("app", ['ngRoute']);
app.directive("ngViewLite", ['$route', '$compile', '$controller', function($route, $compile, $controller){
return {
terminal: true,
priority: 400,
transclude: 'element',
compile : function(element, attr, linker){
return function(scope, $element, attr) {
var currentElement;
scope.$on('$routeChangeSuccess', update);
update();
// update view
function update(){
var locals = $route.current && $route.current.locals,
template = locals && locals.$template;
if(template){
var newScope = scope.$new();
linker(newScope, function(clone){
clone.html(template);
$element.parent().append(clone);
if(currentElement){
currentElement.remove();
}
var link = $compile(clone.contents()),
current = $route.current;
currentElement = clone;
current.scope = newScope;
if (current.controller) {
locals.$scope = newScope;
var controller = $controller(current.controller, locals);
clone.data('$ngControllerController', controller);
clone.children().data('$ngControllerController', controller);
}
link(newScope);
newScope.$emit('$viewContentLoaded');
});
}else{
//清除上一次的视图
}
}
}
}
}
}]);
app.controller('RootCtrl', ['$scope', function($scope){
$scope.title = "Home Page";
}]);
app.controller('CatsCtrl', ['$scope', function($scope){
$scope.title = "Cats Page";
}]);
app.config(['$routeProvider', function($routeProvider){
$routeProvider
.when('/', {
controller : 'RootCtrl',
template : '{{title}}
'
})
.when('/cats', {
controller : 'CatsCtrl',
template : '{{title}}
'
})
.otherwise({
redirectTo : '/'
});
}]);
首先,绑定了一个函数update到事件$routeChangeSuccess;当路由发生变化时,update函数将会被调用。在将函数绑定到事件上之后,我们马上调用update()将初始化内容载入页面中。
update函数会检查对于当前路由是否有定义好的模板,如果有它将会调用linker函数,为它传递一个新的作用域,以及一个毁掉函数。这个回调函数中唯一的参数句式克隆的元素,它的html会被当前路由的模板所替代。克隆的元素接着被追加到具有ng-view-lite属性的div中。在此之后我们将前面的内容从视图中移除。
最后,模板必须被编译($compile(clone.contents())
),同时一个新的作用域会被注入到其中(link(newScope)
)。在这两个步骤之间我们会检查路由是否具有一个相关联的控制器,如果有我们就用这个newScope以及当前路由的本地变量初始化控制器。
ngView运行的很好,但是如果你想要根据url来改变多个视图怎么办。根据文档我们知道在一个应用中ngView只能被使用一次。为了完成我们的ngMultiView,我们需要稍稍修改ngView,并创建一个AngularJS值(MultiViewPaths)来保存urls,views,controllers以及templates之间的映射。
在ngMultiView中,我们需要给指令传递一个参数:
`<div ng-multi-view="secondaryContent"></div>`
在这个指令中,这个属性被称为”panel”。我们在此不绑定”$routeChangeSuccess”事件,而是去绑定”$locationChangeSuccess”事件,来确保我们的指令完全的独立于ngRoute。ngMultiView将会根据下面的方式运行:
var app = angular.module('app', []);
app.value('MultiViewPaths',
{'/' : {
content : {
template : 'Home Page
More Cats!
'
},
secondaryContent : {
template : 'Visitors Online
- {{user}}
',
controller : 'ListUsersCtrl'
}
},
'/cats' : {
content: {
template : 'All Cats
- {{cat}}
',
controller : 'ListCatsCtrl'
},
secondaryContent : {
template : 'Cat of the Minute: {{cat}}
',
controller : 'CatOfTheMinuteCtrl'
}
}
});
app.directive("ngMultiView", ['$rootScope', '$compile', '$controller', '$location', 'MultiViewPaths', function($rootScope, $compile, $controller, $location, MultiViewPaths){
return {
terminal: true,
priority: 400,
transclude: 'element',
compile : function(element, attr, linker){
return function(scope, $element, attr) {
var currentElement,
panel = attr.ngMultiView;
$rootScope.$on('$locationChangeSuccess', update);
update();
// update view
function update(evt, newUrl, oldUrl){
if(!newUrl){ return }
var url = newUrl.match(/#(\/.*)/),
match, template, controller;
match = url ? MultiViewPaths[url[1]] : MultiViewPaths['/'];
template = match[panel].template;
controller = match[panel].controller;
if(template){
var newScope = scope.$new(),
locals = {},
newController = controller;
linker(newScope, function(clone){
clone.html(template);
$element.parent().append(clone);
if(currentElement){
currentElement.remove();
}
var link = $compile(clone.contents());
currentElement = clone;
if (newController) {
locals.$scope = newScope;
var controller = $controller(newController, locals);
clone.data('$ngControllerController', newController);
clone.children().data('$ngControllerController', newController);
}
link(newScope);
newScope.$emit('$viewContentLoaded');
});
}else{
//cleanup last view
}
}
}
}
}
}]);
/* creating the controllers and their data */
app.controller('ListUsersCtrl', ['$scope', function($scope){
$scope.users = ['Lord Nikon', 'Acid Burn', 'Crash Override'];
}]);
app.value('cats', ['Toonces','Stache','Americat','Cassiopeia','Puck','Dica','Vivian','Shosh','Gray','Bashful','Querida','Ignatowski','Aenias','Ramsay','Ishcabible','Guinness','Roux','Gefahr']);
app.controller('ListCatsCtrl', ['$scope', 'cats', function($scope, cats){
$scope.cats = cats;
}]);
app.controller('CatOfTheMinuteCtrl', ['$scope', 'cats', function($scope, cats){
var randIndex = Math.floor(Math.random() * cats.length);
$scope.cat = cats[randIndex];
}]);
我们的ngMultiView指令非常的基本,它不能接受任何由urls传递的参数,也不会处理作用域清除,或者动画。如果你需要更多的功能,你可以修改$route service来让它满足多视图要求。
创建自定义指令一开始会很吓人。有太多的术语等着我们去学习。然而,只要你学习编写了一个指令,后面的学习就会简单很多。