AngularJS简介
加载流程
表达式
指令
Scope(作用域)
模块
控制器
过滤器
服务
路由
ui-router和ngRoute
两种路由模式
路由的事件
数据绑定(AJAX)
理解angular中的module和injector,即依赖注入
理清angularJS中的$injector
、$rootScope
和$scope
的概念和关联关系
去掉URL中的#
禁止模板缓存
angularjs是由Google团队开发的一款非常优秀web前端框架。在当前如此多的web框架下,angularjs能脱颖而出,从架构设计上就高人一等,双向数据绑定,依赖注入,指令,MVC,模板。Angular.js创新地把后台技术融入前端开发,扫去jQuery一度的光芒。用angularjs就像写后台代码,更规范,更结构化,更可控。
AngularJS 应用组成如下:
View(视图), 即 HTML。
Model(模型), 当前视图中可用的数据。
Controller(控制器), 即 JavaScript 函数,可以添加或修改属性。
ng-app 定义了应用程序的根元素,网页加载完毕时会自动引导(自动初始化)应用程序。
ng-init 指令初始化 AngularJS 应用程序变量。
ng-model 指令把输入域的值绑定到应用程序变量 name。定义了控制器
ng-bind 指令把应用程序变量 name 绑定到某个段落的 innerHTML。
AngularJS 表达式写在双大括号内:{{ expression }}。
AngularJS 指令是扩展的 HTML 属性,带有前缀 ng-
ng-repeat 指令会重复一个 HTML 元素
<li ng-repeat="x in names"> {{ x }} </li>
创建自定义的指令:
.directive 函数来添加自定义的指令
runoobDirective, 但在使用它时需要以 - 分割, runoob-directive:
E 只限元素名使用
A 只限属性使用
C 只限类名使用
M 只限注释使用
<runoob-directive></runoob-directive>
<script> var app = angular.module("myApp", []); app.directive("runoobDirective", function() { return { restrict : "A", template : "<h1>自定义指令!</h1>" }; }); </script>
Scope(作用域) 是应用在 HTML (视图) 和 JavaScript (控制器)之间的纽带。
Scope 是一个对象,有可用的方法和属性。
Scope 可应用在视图和控制器上。
在 AngularJS 中,Scope 是 controller 的上下文(view 层可用的数据)。它是可以继承的。也就是说,父 controller 中定义的变量和函数对子 controller 来说也是可用的。但有一个例外:在directive中使用scope: { … },这种方式创建的作用域是一个独立的”Isolate”作用域,它也有父作用域,但父作用域不在其原型链上,不会对父作用域进行原型继承。这种方式定义作用域通常用于构造可复用的directive组件。
提示
一、以下方式会创建新的子作用域,并且进行原型继承:
ng-repeat、ng-include、ng-switch、ng-view、ng-controller, 用scope: true和transclude: true创建directive。
二、以下方式会创建新的独立作用域,不会进行原型继承:
用scope: { … }创建directive。这样创建的作用域被称为”Isolate”作用域。
注意:默认情况下创建directive使用了scope: false,不会创建子作用域。
进行原型继承即意味着父作用域在子作用域的原型链上,这是JavaScript的特性。AngularJS的作用域还存在如下内部定义的关系:
scope.$parent
指向scope的父作用域;
scope.$$childHead
指向scope的第一个子作用域;
scope.$$childTail
指向scope的最后一个子作用域;
scope.$$nextSibling
指向scope的下一个相邻作用域;
scope.$$prevSibling
指向scope的上一个相邻作用域;这些关系用于AngularJS内部历遍,如 broadcast和 emit事件广播,
$digest
处理等。
$scope是一个把view(一个DOM元素)连结到controller上的对象。在我们的MVC结构里,这个$scope
将成为model,它提供一个绑定到DOM元素(以及其子元素)上的excecution context。
尽管听起来有点复杂,但$scope
实际上就是一个JavaScript对象,controller和view都可以访问它,所以我们可以利用它在两者间传递信息。在这个 $scope 对象里,我们既存储数据,又存储将要运行在view上的函数。
根作用域
所有的应用都有一个$rootScope
,它可以作用在 ng-app 指令包含的所有 HTML 元素中。
$rootScope 可作用于整个应用中。是各个 controller 中 scope 的桥梁。用 rootscope 定义的值,可以在各个 controller 中使用。
模块定义了一个应用程序。
模块是应用程序中不同部分的容器。
模块是应用控制器的容器。
控制器通常属于一个模块。
angular.module 函数来创建模块
var app = angular.module("myApp", []);
angular.module(‘myModule’, []) : 创建一个新的module,覆盖已经存在的module
angular.module(‘myModule’) : 指向已经存在的module
ng-controller 指令定义了应用程序控制器。
控制器是 JavaScript 对象,由标准的 JavaScript 对象的构造函数 创建。
<script> var app = angular.module('myApp', []); app.controller('myCtrl', function($scope) { $scope.firstName = "John"; $scope.lastName = "Doe"; }); </script>
filter
过滤器可以使用一个管道字符(|)添加到表达式和指令中。
AngularJS 过滤器可用于转换数据:
currency : 格式化数字为货币格式
filter : 从数组项中选择一个子集
lowercase : 格式化字符串为小写
orderBy : 根据某个表达式排列数组
uppercase : 格式化字符串为大写
表达式中添加过滤器
<p>姓名为 {{ lastName | uppercase }}</p>
向指令添加过滤器
<li ng-repeat="x in names | orderBy:'country'"> {{ x.name + ', ' + x.country }} </li>
一、AngularJS Provider/Service/Factory基础介绍
1、什么是 provider ?
provider 可以为应用提供通用的服务,形式可以是常量,也可以是对象。
比如我们在 controller 里注入进来的 http, scope 都可以认为是 provider。
app.controller('MainCtrl', function ($scope, $http) {
$http.get(....).then(.....);
}
2、provider
现在让我们自己来定制一个 provider
// 方法 1
$provide.provider('test', {
n:1;
$get: function () {
return n;
};
});
// 方法 2
$provide.provider('test', function () {
this.n = 2;
this.$get = function () {
return n;
};
});
// 使用
app.controller('MainCtrl', function ($scope, test) {
$scope.test = test;
});
让我们看看 provider 的内部实现代码
function provider(name, provider_) {
if (isFunction(provider_)) {
provider_ = providerInjector.instantiate(provider_);
}
if (!provider_.$get) {
throw Error('Provider ' + name + ' must define $get factory method.');
}
return providerCache[name + providerSuffix] = provider_;
}
可以看到 provider 的基本原则就是通过实现 get方法来进行单例注入,使用时获得的就是 get 执行后的结果。
3、factory
那如果每次都要写一个 $get 是不是很麻烦? OK,所以我们有了 factory。 factory 可以说是 provider 的变种, 方法中的第二个参数就是 $get
中的内容。
// 定义 factory
$provide.factory('dd', function () {
return new Date();
});
// 使用
app.controller('MainCtrl', function ($scope, dd) {
$scope.mydate = dd;
});
factory 的实现源代码:
function factory(name, factoryFn) {
return provider(name, {
$get: factoryFn
});
}
4、service
在 factory 的例子中我们还是需要 new 一个对象返回,而 service 就更简单了,这一步都帮你省了, 他的第二个参数就是你要返回的对象类, 也就是 new 的哦给你工作都不用你做了。够清爽吧?
// 定义 service
$provide.service('dd', Date);
下面是 service 的实现源代码:
function service(name, constructor) {
return factory(name, ['$injector', function($injector) {
return $injector.instantiate(constructor);
}]);
}
然后 factory 和 service 带来代码精简的同时也损失了一些特性。
provider 定义的服务是可以通过模块 config 来配置的。
二、详细介绍
Services都是单例的,就是说在一个应用中,每一个Serice对象只会被实例化一次(用$injector
服务),主要负责提供一个接口把特定函数需要的方法放在一起,我们就拿见过的$http Service
来举例,他就提供了访问底层浏览器的XMLHttpRequest对象的方法,相较于调用底层的XMLHttpRequest对象,$http API使用起来相当的简单。
Angular内建了很多服务供我们日常使用,这些服务对于在复杂应用中建立自己的Services都是相当有用的。
AngularJS让我们可以轻松的创建自己的services,仅仅注册service即可,一旦注册,Angular编译器就可以找到并加载他作为依赖供程序运行时使用
最常见的创建方法就是用angular.module API 的factory模式
angular.module('myApp.services', [])
.factory('githubService', function() {
var serviceInstance = {};
// 我们的第一个服务
return serviceInstance;
});
当然,我们也可以使用内建的$provide service来创建service。
这个服务并没有做实际的事情,但是他向我们展示了如何去定义一个service。创建一个service就是简单的返回一个函数,这个函数返回一个对象。这个对象是在创建应用实例的时候创建的(记住,这个对象是单例对象)
接下来让我们添加一些有实际意义的代码去调用GitHub的API:
angular.module('myApp.services', [])
.factory('githubService', ['$http', function($http) {
//---定义函数
var doRequest = function(username, path) {
return $http({
method: 'JSONP',
url: 'https://api.github.com/users/' + username + '/' + path + '?callback=JSON_CALLBACK'
});
}
//---返回
return {
events: function(username) { return doRequest(username, 'events'); },
};
}]);
我们创建了一个只有一个方法的GitHub Service,events可以获取到给定的GitHub用户最新的GitHub事件,为了把这个服务添加到我们的controller中。我们建立一个controller并加载(或者注入)githubService作为运行时依赖,我们把service的名字作为参数传递给controller 函数(使用中括号[])
app.controller('ServiceController', ['$scope', 'githubService',
function($scope, githubService) {
// Watch for changes on the username property.
// If there is a change, run the function
$scope.$watch('username', function(newUsername) {
// uses the $http service to call the GitHub API
// and returns the resulting promise
githubService.events(newUsername)
.success(function(data, status, headers) {
// the success function wraps the response in data
// so we need to call data.data to fetch the raw data
$scope.events = data.data;
})
});
}]);
请注意,这种依赖注入的写法对于js压缩是安全的,我们会在以后的章节中深入导论这件事情。
我们的githubService注入到我们的ServiceController后,我们就可以像使用其他服务(我们前面提到的$http服务)一样的使用githubService了。
三、下面列出服务实例
在 AngularJS 中,服务是一个函数或对象,可在你的 AngularJS 应用中使用。
AngularJS 内建了30 多个服务。
$location
AngularJS 使用 $location
服务比使用 window.location 对象更好。
$location服务初始化好以后,你就可以使用jquery风格的读写器和它交互了,你可以获取或者改变当前URL。
$location
服务的配置
要配置 location服务,检索 locationProvider并把参数设置成以下这样:
html5Mode(模式): {boolean}
true - 参阅HTML5模式
false - 参阅Hashbang模式
default: false
hashPrefix(前缀): {string}
Hashbang URLs的前缀 (在Hashbang模式中或者低级浏览器中使用)
default: ‘!’
配置示例
$locationProvider.html5Mode(true).hashPrefix('!');
读写器(getter and setter)
你可以给$location服务传递特殊字符,它会根据RFC 3986规则来编码。当你调用写方法时:
所有传递给写方法(如path(), search(), hash())的值都会被编码。
-
// get the current path
$location.path();
// change the path
$location.path('/newValue')
$location.path('/newValue').search({key: value});
$location服务有一个特殊的replace方法可以用来告诉
$lacation
服务下一次自动和浏览器同步,上一条浏览记录应该被替换而不是创建一个新的。这在重定向的时候很好用。不这样的话容易使后退按钮失效(点后退时会又触发重定向)。要改变URL而不添加新的历史记录,你可以这样做:
$location.path('/someNewPath');
$location.replace();
// or you can chain these as: $location.path('/someNewPath').replace();
1.暴露当前地址栏的URL,这样你就能
2.当出现以下情况时同步URL
一系列方法来获取URL对象的具体内容用(protocol, host, port, path, search, hash).formatDate
在URL改变时,不要刷新整个页面。一定要的话,用低级的API,$window.location.href。
$location服务职能让你改变URL;不能让你重新加载页面。
var app = angular.module('myApp', []);
app.controller('customersCtrl', function($scope, $location) {
$scope.myUrl = $location.absUrl();
});
$location服务有两种用来控制地址栏URL格式的配置:Hashbang模式(默认)和HTML5模式(使用HTML5历史API)。
应用会使用两种模式中相同的API,并且$location服务会使用需要的URL片段和浏览器API来帮助改变URL或者进行历史管理。
支持网络爬虫
你需要添加特别的meta标记在你的文档的头部才能支持对你的AJAX应用的索引。
<meta name="fragment" content="!" />
这能让网络爬虫请求带有escaped_fragment形式的参数链接,这样你就能识别爬虫并且返回一个HTML的快照了。
$http 服务向服务器发送请求,应用响应服务器传送过来的数据。
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $http) {
$http.get("welcome.htm").then(function (response) {
$scope.myWelcome = response.data;
});
});
$timeout 服务对应了 JS window.setTimeout 函数。
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $timeout) {
$scope.myHeader = "Hello World!";
$timeout(function () {
$scope.myHeader = "How are you today?";
}, 2000);
});
$interval服务对应了 JS window.setTimeout 函数。
var app = angular.module('myApp', []);
app.controller('myCtrl', function($scope, $timeout) {
$scope.myHeader = "Hello World!";
$timeout(function () {
$scope.myHeader = "How are you today?";
}, 2000);
});
创建自定义服务创建自定义的访问,链接到你的模块中:
创建名为hexafy 的访问:
app.service('hexafy', function() {
this.myFunc = function (x) {
return x.toString(16);
}
});
使用自定义的的服务 hexafy 将一个数字转换为16进制数:
app.controller('myCtrl', function($scope, hexafy) {
$scope.hex = hexafy.myFunc(255);
});
过滤器中,使用自定义服务,当你创建了自定义服务,并连接到你的应用上后,你可以在控制器,指令,过滤器或其他服务中使用它。
在过滤器 myFormat 中使用服务 hexafy:
app.filter('myFormat',['hexify', function(hexify) {
return function(x) {
return hexify.myFunc(x);
};
}]);
咱们可以通过在主页面中引入不同的模板来支持不同页面的切换,但是这么做的缺点就是,越来越多的内嵌代码导致最后难以管理。
通过ng-include指令我们可以把很多的模板整合在视图中,但是我们有更好的方法来处理这种情况,我们可以把视图打散成layout和模板视图,然后根据用户访问的特定的URL来显示需要的视图
我们可以将这些“碎片”在一个布局模板中拼接起来
AngularJS通过在$routeProvider($route服务的提供者)
上声明routes来实现上面的构想
使用$routeProvider,我们可以更好的利用浏览历史的API并且可以让用户可以把当前路径存成书签以方便以后的使用
在我们的应用中设定路由,我们需要做两件事情:
第一,我们需要指出我们存放将要存放新页面内容的布局模板在哪里。比如,如果我们想在所有页面都配上header和footer,我们可以这样设计布局模板:
<header>
<h1>Header</h1>
</header>
<div class="content">
<div ng-view></div>
</div>
<footer>
<h5>Footer</h5>
</footer>
ng-view指令将高速$routeProvider在哪里渲染模板
$routeProvider
提供了两种方法处理路由:when 和 otherwise。
方法when接收两个参数:
第一个设置$location.path(). (直接用“//”也没有问题)
第二个参数是配置对象,这个可以包含不同的键,
- path比较好理解,也就是路由路径,和$location.path
匹配,后面带上:则表示参数,可以传递给$routeParams。
- route指的是path匹配后的动作,是一个对象,属性有:
- controller (string/function):在这里声明的控制器会得到路由创建的作用域。
- template (string):HTML模板渲染到声明了ng-view的元素里。
- templateURL (string):功能和template一样,只是通过XHR获得模板。
- resolve : 将列表对象注入到controller中。
- redirectTo (string/function): 用于替换path。比起用作字符串,函数更有意义。
- reloadOnSearch (boolean):默认是true,也就是
$location.search()
发生变化时重新加载路由。
我们可以简单的说几个
controller
controller: 'MyController'
// or
controller: function($scope) {
// ...
}
如果在配置对象中设置了controller属性,那这个controller会在route加载的时候实例化,这个属性可以是一个字符串(必须在module中注册过的controller)也可以是controller function.
Template模板
template: '<div><h2>Route</h2></div>'
如果我们在配置对象的template属性设置了值,那么模板就会被渲染到DOM中的ng-view处
templateUrl
templateUrl: 'views/template_name.html'
如果我们在配置对象的templateUrl属性中设置了值,AngularJS将通过XHR来获取该模板并把模板内容渲染到DOM中的ng-view处
值得注意的是:templateUrl属性跟其他AngularJS XHR请求的处理流程是一样的,也就是说,即使用户从这个页面离开,等他再回到这个页面,应用不会再去请求这个模板页面,因为$templateCache已经缓存了这个模板
添加一些路由
angular.module('myApp', []).
config(['$routeProvider', function($routeProvider) {
$routeProvider.when('/', {
controller: 'HomeController',
template: '<h2>We are home</h2>'
})
.otherwise({redirectTo: '/'});
}]);
url带参数的情况
$routeProvider
还可以处理URL里的传递的参数(比如,/people/42, 假设42是我们要找的people的id号) 只需要简单在字符串前加上 ‘:’,$routeProvider
会尝试匹配URL中id并把id作为key在$routeParams服务中使用
$routeProvider.when('/person/:id', {
controller: 'PeopleController',
template: '<div>Person show page: {{ name }}</div>'
})
在PeopleController中,我们检索路由中指定的people的:id
app.controller('PeopleController', function($scope, $routeParams) {
// We now have access to the $routeParams
// At the route /person/42, our $routeParams will look like:
// { id: 42 }
});
<!DOCTYPE html>
<!-- define angular app -->
<html ng-app="scotchApp">
<head>
<!-- SCROLLS -->
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css" />
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/font-awesome/4.0.0/css/font-awesome.css" />
<!-- SPELLS -->
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.2.25/angular-route.js"></script>
<script src="script.js"></script>
</head>
<!-- define angular controller -->
<body ng-controller="mainController">
<nav class="navbar navbar-default">
<div class="container">
<div class="navbar-header">
<a class="navbar-brand" href="/">Angular Routing Example</a>
</div>
<ul class="nav navbar-nav navbar-right">
<li><a href="#"><i class="fa fa-home"></i> Home</a></li>
<li><a href="#about"><i class="fa fa-shield"></i> About</a></li>
</ul>
</div>
</nav>
<div id="main">
<!-- angular templating -->
<!-- this is where content will be injected -->
<div ng-view></div>
</div>
<footer class="text-center">
<p>View the tutorial on <a href="http://scotch.io/tutorials/javascript/single-page-apps-with-angularjs-routing-and-templating">Scotch.io</a></p>
<p>View a tutorial on <a href="http://scotch.io/tutorials/javascript/animating-angularjs-apps-ngview">Animating Your Angular Single Page App</a></p>
</footer>
</body>
</html>
// create the module and name it scotchApp
var scotchApp = angular.module('scotchApp', ['ngRoute']);
// configure our routes
scotchApp.config(function($routeProvider) {
$routeProvider
// route for the home page
.when('/', {
templateUrl : 'pages/home.html',
controller : 'mainController'
})
// route for the about page
.when('/about', {
templateUrl : 'pages/about.html',
controller : 'aboutController'
});
});
// create the controller and inject Angular's $scope
scotchApp.controller('mainController', function($scope) {
// create a message to display in our view
$scope.message = 'Everyone come and see how good I look!';
});
scotchApp.controller('aboutController', function($scope) {
$scope.message = 'Look! I am an about page.';
});
<div class="jumbotron text-center"> <h1>About Page</h1> <p>{{ message }}</p> </div>
<div class="jumbotron text-center"> <h1>Contact Page</h1> <p>{{ message }}</p> </div>
目录
- AngularJS路由介绍
- 路由的代码实现
- 实现效果截图
- AngularJS路由介绍
AngularJS路由功能是一个纯前端的解决方案,与我们熟悉的后台路由不太一样。后台路由,通过不同的URL会路由到不同的控制器上(controller),再渲染(render)到页面(HTML)。AngularJS的前端路由,需求提前对指定的(ng-app),定义路由规则(routeProvider),然后通过不同的URL,告诉(ng-app)加载哪个页面(HTML),再渲染到(ng-app)视图(ng-view)中。
AngularJS的前端路由,虽然URL输入不一样,页面展示不一样,其实完成的单页(ng-app)视图(ng-view)的局部刷新。这样来看,AngularJS做单页应用就有点标配的感觉了。
2 路由的代码实现
业务场景:论坛功能,帖子列表页(list.html) 和 帖子内容页(detail.html)。
代码文件:
1). 增加:app/demo-route.html
这个文件是主页面(ng-app),包含视图(ng-view)
<html ng-app="routeApp" ng-controller="myCtrl"> //----ng-app,(ng-controller可不写!!!!!!!!)
<head>
<meta charset="utf-8">
<title>route</title>
</head>
<body >
<h1>Route Demo index</h1>
<div ng-view></div> //-----ng-view!!!!!!!!!
<script src="bower_components/angular/angular.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/controllers/main.js"></script>
</body>
</html>
2). 增加:app/views/route/list.html
这个页面是布局模板,是HTML的代码片段。包括了一组ID的列表,通过ID列表的链接,可以进入到ID的详细页面。
<h3>Route : List.html</h3> <ul> <li ng-repeat="id in [1, 2, 3 ]"> <a href="#/list/{{ id }}"> ID{{ id }}</a> </li> </ul>
3). 增加:app/views/route/detail.html
这个页面是布局模板,是HTML的代码片段。通过ID访问,包含ID号, (ID的文章内容)
<h3>Route <span style="color: red;">{{id}}</span>: detail.html </h3>
4). 修改: app/scripts/app.js
这个是ng-app文件的定义,我们在demo-route.html中定义了routeApp,在这里需要声明。
var routeApp = angular.module('routeApp', ['ngRoute']);
//----定义一个全局的控制器!!!!(可选,下面针对js文件的引入,每次刷新重新加载)
//myApp.controller('myCtrl', function($scope) {
// $scope.loadScript = function(url, type, charset) {
// if (type === undefined) type = 'text/javascript';
// if (url) {
// var script = document.querySelector("script[src*='" + url + "']");
// var heads = document.getElementsByTagName("head");
// if (heads && heads.length) {
// var head = heads[0];
// if (head) {
// if (script) {
// head.removeChild(script);
// }
// script = document.createElement('script');
// script.setAttribute('src', url);
// script.setAttribute('type', type);
// if (charset) script.setAttribute('charset', charset);
// head.appendChild(script);
// }
// }
// return script;
// }
// };
//});
routeApp.config(['$routeProvider',function ($routeProvider) {
$routeProvider
.when('/list', {
templateUrl: 'views/route/list.html',
controller: 'RouteListCtl'
})
.when('/list/:id', {
templateUrl: 'views/route/detail.html',
controller: 'RouteDetailCtl'
})
.otherwise({
redirectTo: '/list'
});
}]);
在routeApp模块中,我们定义了路由和布局模板。routeApp的默认URL是/list,即http://localhost:9000/demo-route.html#/list。 跳转详细页的路由是/list/:id,id为参数。
同时,/list的布局模板是views/route/list.html,属于RouteListCtl的控制器管理空间。
5). 修改: app/scripts/controllers/main.js
这个文件定义控制器controller。
var quanju_temp; //---------定义全局变量
routeApp.controller('RouteListCtl',function($scope) {
});
routeApp.controller('RouteDetailCtl',function($scope, $routeParams) {
quanju_temp=$scope;//---------把$scope设为全局变量,下面moment.min.js中就可以使用quanju_temp($scope)
$scope.id = $routeParams.id;
//可以在此动态加js文件
//$scope.$parent.loadScript('js/lib/moment.min.js', 'text/javascript', 'utf-8');
//$("head title").text("实例");
});
分别对应该路由中的两个控制器声明。
我们了解 angular.js 是一种富客户端单页面应用,所以要在一个页面呈现不同的视图,路由起到了至关重要的作用.
angular.js 为我们封装好了一个路由工具 ngRoute ,它是一种靠url改变去驱动视图.
angularUI 也为我们封装了一个独立的路由模块 ui-router ,它是一种靠状态 state 来驱动视图.
后者有什么优势:一个页面可以嵌套多个视图,多个视图去控制某一个视图等.
ngRoute
使用时需要ui中用ng-view指令指定 如:
<div ng-view></div>
url改变此区域会被刷新.
ui-router
使用时需要ui中用ui-view指令指定 如:
var app = angular.module(“YIJIEBUYI", [‘ui.router']);
app.config(function () {
//路由配置
});
路由设置:
angular.module(‘YIJIEBUYI').config(['$stateProvider', function($stateProvider) { $stateProvider.state('blog',{ url:'/blog', views:{ 'container':{templateUrl:'templates/blog/layout.html'} } }).state('blog.index',{ url:'/index', views:{ 'container':{templaterUrl:'templates/blog/index.html'} } }) });
ngRoute 和 ui-route 相比:
$route
—> $state
$routeParams
—> $stateParams
$routeProvider
—> $stateProvider
<div ng-view></div>
—> <div ui-view></div>
设置路由相比:
$urlRouterProvider.otherwise('/blog/index')
; 设置默认路由还需要使用ngRoute来设置.
$stateProvider.state(‘blog.index', {url:’….’,views:{模板路径});
见上面设置信息.
设置默认页还是要用到 ngRoute工具.
下面详细说下 ui-route 使用:
(1)父路由,子路由
ui-route子路由可以继承父路由,也就是说 state 设置可以嵌套,通过名称中的.(点)来区分层次.
如下面路由:
angular.module(‘YIJIEBUYI').config(['$stateProvider', function($stateProvider) { $stateProvider.state('blog',{ url:'/blog', views:{ 'container':{templateUrl:'templates/blog/layout.html'} } }).state('blog.index',{ url:'/index', views:{ 'container':{templaterUrl:'templates/blog/index.html'} } }) });
blog 对应的路由是 /blog
blog.index 对应的路由就是 /blog/index (前面的/blog就是从父view中继承过来的)
blog.index 就是 blog的子view
(2)指定响应的view
<div ui-view="view1"></div>
<div ui-view="view2"></div>
.state("blog.detail"),{
url:”/:blogID", views:{ view1:{ templateUrl:"view1.html" } ,view2:{ templateUrl:"view1.html" } } }
(3) state 配置参数
- url:默认相对路径(以^开头的是绝对路径)
- views:每个子视图可以包含自己的模板、控制器和预载入数据。 (后2项选填,控制器可以在view中绑定)
- abstract:抽象模板不能被激活
- template: HTML字符串或者返回HTML字符串的函数
如:
$stateProvider.state(‘blog.detail', { template: '<h1>My Blog</h1>' })
$stateProvider.state(‘blog.detail', { templateUrl: ’templates/blog_detail.html'
})
$stateProvider.state(‘blog.detail', {
templateProvider: function ($timeout, $stateParams) {
return $timeout(function () {
return '<h1>' + $stateParams.blogID + '</h1>'
}, 100);
}
})
(4)解决器 Resolve
可以使用 Resolve 为控制器提供可选的依赖注入项。
Relolve 是由 key/value 组成的键值对象.
key – {string}:注入控制器的依赖项名称。
value - {string|function}:
string:一个服务的别名
function:函数的返回值将作为依赖注入项,如果函数是一个耗时的操作,那么控制器必须等待该函数执行完成(be resolved)才会被实例化。
比如,博客后台的视图都需要登录用户才能访问,那么判断是否登录就可以做成一个控制器依赖
$stateProvider.state(‘YIJIEBUYI', { url: “/admin", // 登录后才能访问 resolve: {authentication:[‘YijiebuyiAuth', '$q', function(YijiebuyiAuth, $q){
return $q.when().then(function(){
return YijiebuyiAuth.authentication();
});
}]},
views: {
container: { templateUrl: “templates/admin_manage.html" } } })
在上面的返回函数中我们注入了一个服务 YijiebuyiAuth ,这个服务里实现了登录判断的方法 authentication
(5)$state 对象提供自定义数据
$stateProvider
.state(‘blog.index', { templateUrl: ’templates/blog_index.html',
data: {
current_page: 1,
page_size: 20
}
})
上面 data 对象就是自定义数据,
里面定义了2页面的当前页和显示内容条数
在视图对应的 controller 中我们就可以通过下面的方法来获取自定义数据.
console.log($state.current.data.current_page); // 1
console.log($state.current.data.page_size); // 20
(6) onEnter 和 onExit 回调函数
onEnter: 当状态活跃时触发 什么是活跃???页面正在加载中…..我也求解!
onExit : 当状态不活跃时触发 什么是不活跃?? 页面加载完成…同求解!
$stateProvider.state("blog.detail", {
template: '<h1>blog</h1>',
resolve: { title: '一介布衣' },
controller: function($scope, title){
$scope.title = title;
},
// title 是解决依赖项注入控制器
onEnter: function(title){
if(title){ ... do something ... }
},
// title 是解决依赖项注入控制器
onExit: function(title){
if(title){ ... do something ... }
}
})
所以,刚才上面做的解决依赖判断是否登录,完全可以在 onEnter 事件中判断登录状态,如果未登录,直接跳转到其他路由即可.
(7) 页面跳转
<a href="#/blog/1234”>博客详情</a> <a ui-sref=“blog.detail({blogID:blogID})”>博客详情</a> $state.go(‘blog.detail', {blogID:blogID});
(8) 事件
myApp.run(function($rootScope, $templateCache) {
$rootScope.$on('$stateChangeStart', function(event, toState, toParams, fromState, fromParams){ ... })
$rootScope.$on('$stateNotFound', function(event, unfoundState, fromState, fromParams){ ... })
$rootScope.$on('$stateChangeSuccess', function(event, toState, toParams, fromState, fromParams){ ... })
$rootScope.$on('$stateChangeError', function(event, toState, toParams, fromState, fromParams, error){ ... })
}
View被加载但是DOM树构建之前时:
$scope.$on('$viewContentLoading', function(event, viewConfig){ ... });
View被加载而且DOM树构建完成时:
$scope.$on('$viewContentLoaded', function(event){ ... });
如果你看过官网的路由Demo,应该会发现在URL中包含#符号.之所以有这个东西是因为AngularJS一些默认情况下的Demo都是基于路由的标签模式.不同路由模式在浏览器地址栏中会使用不同的URL格式.$location
默认是标签模式.
(1) 标签模式
标签模式就利用内部链接的技巧,URL路径以#符号开头.AngularJS本身不会重写标签,也不需要进行任何配置或者服务器支持.它的URL看起来类似这样:http://angular.app.com/#/users
默认的AngularJS配置就是这样的,不要任何设置.通常标签模式是HTML5模式的一种降级方案.
(2) HTML5模式
HTML5模式就接近于我们正常的URL,同一个地址它看起来是这样的:http://angular.app.com/users. 在AngularJS内部,$location
服务通过HTML5历史API让应用能够使用普通的URL路径来访问路由,当浏览器不支持HTML5历史API时,$location服务会自动使用标签模式的URL作为替代 方案.在HTML5模式中,AngularJS会负责重写中的链接.也就是说AngularJS会根据浏览器的能力在编译时决定是否要重写href=”“中的链接.
后端服务器也需要支持URL重写,服务器需要确保所有请求都返回index.html,以支持HTML5模式.这样才能确保由AngularJS应用来处理路由.
当在HTML5模式的AngularJS中写链接时,永远都不要使用相对路径.如果你的应用是在根路径中加载的,这不会有什么问题,但如果是在其他路径中,AngularJS应用就无法正确处理路由了.
事件这个词在前端出现的频率真是高,根本拦不住,哪哪都是.$route服务在路由过程中的每个阶段都会触发不同的事件,可以为这些不同的路由事件设置监听器并做出响应.
一共有4个事件用来监听路由的状态变化: $routeStartChange
, $routeChangeSuccess
, $routeChangeError
, $routeUpdate
.
其中最常用的是前两个,这里稍微解释一下.
(1) $routeStartChange
看名字就能猜出来它表示的是路由开始变化的事件,在浏览器地址栏发生变化之前AngularJS会先广播一下这个事件.路由会开始加载所有需要的依赖,模板和resolve部分的内容也会注入.
angular.module('myApp', [])
.run(['$rootScope', '$location', function($rootScope, $location){
$rootScope.$on('$routeChangeStart', function(evt, next, current){
console.log('route begin change');
});
}]);
解释一下事件的参数,evt是事件对象,可以从中读取到一些route的信息.next是将要导航到的路由,current是当前的URL.
可以看见在这个时期我们可以做很多有用的事,因为此时仅仅是路由开始变化,对应的内容都还没来得及发生改变.
这里我们可进行permission的校验,loading画面的加载,对应路由信息的读取等等.
(2) $routeChangeSuccess
在路由的所有依赖都被注入加载后,AngularJS会对外广播路由跳转成功的事件.
angular.module('myApp', [])
.run(['$rootScope', '$location', function($rootScope, $location) {
$rootScope.$on('$routeChangeSuccess', function(evt, current, previous) {
console.log('route have already changed');
});
}])
这里也稍微解释下三个参数,evt是AngularJS事件对象,current是当前所处路由,previous是上一个路由.
通过把一个文本输入框绑定到person.name属性上,就能把我们的应用变得更有趣一点。这一步建立起了文本输入框跟页面的双向绑定。
View <—->$scope<—->controller
在这个语境里“双向”意味着如果view改变了属性值,model就会“看到”这个改变,而如果model改变了属性值,view也同样会“看到”这个改变。Angular.js 为你自动搭建好了这个机制。
要建立这个绑定,我们在文本输入框上使用ng-model 指令属性,像这样:
<div ng-controller="MyController"> <input type="text" ng-model="person.name" placeholder="Enter your name" /> <h5>Hello {{ person.name }}</h5> </div>
现在我们建立好了一个数据绑定(没错,就这么容易)
当你在文本框里输入时,下面的名字也自动随之改变,这就展现了我们数据绑定的一个方向:从view到model。
我们也可以在我们的(客户端)后台改变model,看这个改变自动在前端体现出来。要展示这一过程,让我们在 MyController 的model里写一个计时器函数, 更新 scope上的一个数据。下面的代码里,我们就来创建这个计时器函数,它会在每秒计时(像钟表那样),并更新 scope 上的clock变量数据:
app.controller('MyController', function($scope) {
$scope.person = { name: "Ari Lerner" };
var updateClock = function() {
$scope.clock = new Date();
};
var timer = setInterval(function() {
$scope.$apply(updateClock);
}, 1000);
updateClock();
});
可以看到,当我们改变model中clock变量的数据,view会自动更新来反映此变化。用大括号我们就可以很简单地让clock变量的值显示在view里:
<div ng-controller="MyController"> <h5>{{ clock }}</h5> </div>
前面我们把数据绑定在了文本输入框上。请注意, 数据绑定并非只限于数据,我们还可以利用绑定调用 $scope 中的函数(这一点之前已经提到过)。
对按钮、链接或任何其他的DOM元素,我们都可以用另一个指令属性来实现绑定:ng-click 。这个 ng-click 指令将DOM元素的鼠标点击事件(即 mousedown 浏览器事件)绑定到一个方法上,当浏览器在该DOM元素上鼠标触发点击事件时,此被绑定的方法就被调用。跟上一个例子相似,这个绑定的代码如下:
<div ng-controller="DemoController"> <h4>The simplest adding machine ever</h4> <button ng-click="add(1)" class="button">Add</button> <button ng-click="subtract(1)" class="button">Subtract</button> <h4>Current count: {{ counter }}</h4> </div>
不论是按钮还是链接都会被绑定到包含它们的DOM元素的controller所有的 $scope 对象上,当它们被鼠标点击,Angular就会调用相应的方法。注意当我们告诉Angular要调用什么方法时,我们将方法名写进带引号的字符串里。
app.controller('DemoController', function($scope) {
$scope.counter = 0;
$scope.add = function(amount) { $scope.counter += amount; };
$scope.subtract = function(amount) { $scope.counter -= amount; };
});
AJAX
Angular.js原生支持AJAX,由此我们就获得了与一个或多个服务器来回发送请求的能力。这个能力对我们要创建的这种客户端应用来说是至关重要的,因为这种应用需要跟服务器交流,获取和更新数据。
Angular.js通过一个服务来支持AJAX(在之后的章节我们会讨论这个服务),这个服务就叫做 http服务。所有Angular.js的核心服务都用 前缀,这点在之前的 $scope 服务里我们已经见过了。
在深入过多细节之前,让我们先来用 $http 服务创建一个请求:
$http({
method: 'JSONP',
url: 'http://api.openbeerdatabase.com/v1/beers.json?callback=JSON_CALLBACK'
}).success(function(data, status, headers, config) {
// data contains the response
// status is the HTTP status
// headers is the header getter function
// config is the object that was used to create the HTTP request
}).error(function(data, status, headers, config) {
});
$http 服务是Angular.js的核心服务之一,它帮助我们通过XMLHttpRequest对象或JSONP与远程HTTP服务进行交流。
注意, 像上面例子中那样,原封不动加上以下字符串 callback=JSON_CALLBACK ,Angular.js就会负责为你处理JSONP请求,将 JSON_CALLBACK 替换成一个合适的回调函数。
http服务是这样一个函数:它接受一个设置对象,其中指定了如何创建HTTP请求;它将返回一个承诺(∗参考JavaScript异步编程的promise模式),其中提供两个方法:success方法和error方法。像我们刚才做的那样,让我们调用 http 服务来创建一个请求,这一次是为了取得所有音频文件。我们想让这个服务在controller实例化时启动,所以我们只需要把这个方法直接放在controller函数里(这个函数在controller被创建时就会被调用),像这样:
var apiKey = 'YOUR_KEY',
nprUrl = 'http://api.npr.org/query?id=61&fields=relatedLink,title,byline,text,audio,image,pullQuote,all&output=JSON';
app.controller('PlayerController', function($scope, $http) {
// Hidden our previous section's content
// construct our http request
$http({
method: 'JSONP',
url: nprUrl + '&apiKey=' + apiKey + '&callback=JSON_CALLBACK'
}).success(function(data, status) {
// Now we have a list of the stories (data.list.story)
// in the data object that the NPR API
// returns in JSON that looks like:
// data: { "list": {
// "title": ...
// "story": [
// { "id": ...
// "title": ...
}).error(function(data, status) {
// Some error occurred
});
});
现在我们在success函数的data里有了一个音频文件的列表。在success回调函数里,把这个列表存储在 scope对象,这样我们就简单地把它绑定在了 scope 对象上:
// from above
}).success(function(data, status) {
// Store the list of stories on the scope
// from the NPR API response object (described above)
$scope.programs = data.list.story;
}).error(function(data, status) {
// Some error occurred
现在,跟刚才一样, 只需在view里访问programs,我们就能在view里访问这个data。你看,使用Angular.js的一个好处就是,当承诺模式返回成功结果时,Angular.js就会自动把这个结果填进你的view里。
<div ng-controller="PlayerController"> {{ programs }} </div>
目录
1.URL的#号问题
使用AngularJS的朋友都应该了解,AngularJS框架定义了自己的前端路由控制器,通过不同URL实现单面(ng-app)对视图(ng-view)的部署刷新,并支持HTML5的历史记录功能,详细介绍可以参考文章:AngularJS路由和模板。
对于默认的情况,是不启动HTML5模式的,URL中会包括一个#号,用来区别是AngularJS管理的路径还是WebServer管理的路径。
比如:下面的带#号的URL,是AngularJS管理的路径。
http://onbook.me/
http://onbook.me/#/
http://onbook.me/#/book
http://onbook.me/#/about
这种体验其实是不太友好的,特别是像我这种喜欢简洁设计的人,#号的出现非我自愿的,怎么看怎么难受。AngularJS框架提供了一种HTML5模式的路由,可以直接去掉#号。
通过设置$locationProvider.html5Mode(true)就行了。
book.config([‘ routeProvider′,′ locationProvider’, function ( routeProvider, locationProvider) {
//..省略代码
$locationProvider.html5Mode(true);
}]);
支持HTML5的路由URL。
http://onbook.me/
http://onbook.me/book
http://onbook.me/about
接下来的问题就来了,当用这种方式设置了路径以后。如果用户从首页(http://onbook.me/)开始访问,然后跳转到 图书页(http://onbook.me/book)一切正常。但如果用户直接打开 图书页(http://onbook.me/book) ,就会出现404错误。
url1
就是这个问题纠结了我好长时间,让我不得不用带#号的URL。
- 找到错误原因
那么,这个问题的原因出在哪里了呢? 在路径解析上出错了。
让我从头说起,AngularJS是单页应用,一个ng-app对应一个页面,一个URL。AngularJS实现了自己的前端路由,让一个ng-app可以管理多个URL,再对应到多个ng-vew上面。当我们去访问URL(http://onbook.me/book) 的时候,怎么确定这个路径是 WebServer 后台管理的URL还是AngularJS前台管理的URL呢?
分2种情况看:
实现起来分为2种解决方案:
1. 静态网站:纯前台网站(JS+HTML+CSS),通过Nginx提供Web服务。
2. 动态网站:前台(JS + HTML + CSS) + 后台Node.js提供Web服务。
静态网站,我们需要修改的地方包括3个文件
index.html : ng-app的定义文件
app.js : 对应ng-app的控制文件
nginx.conf : nginx的网站配置文件
编辑 index.html,增加base标签。
<html lang="zh-CN" ng-app="book">
<head>
<base href="/">
// 省略代码
编辑app.js,增加 $locationProvider.html5Mode(true);
book.config(['$routeProvider', '$locationProvider', '$sceProvider', 'tplProvider', function ($routeProvider, $locationProvider, $sceProvider, tplProvider) {
$routeProvider
.when('/', {templateUrl: tplProvider.html('welcome'), controller: 'WelcomeCtrl'})
.when('/book', {templateUrl: tplProvider.html('book'), controller: 'BookCtrl'}) //图书
.when('/book-r1', {templateUrl: tplProvider.html('book-r1'), controller: 'BookR1Ctrl'}) //R的极客理想
.when('/video', {templateUrl: tplProvider.html('video'), controller: 'VideoCtrl'}) //视频
.when('/about', {templateUrl: tplProvider.html('about'), controller: 'AboutCtrl'}) //关于作者
.otherwise({redirectTo: '/'});
$locationProvider.html5Mode(true);
}]);
编辑nginx的配置文件,增加try_files配置。
server {
set $htdocs /www/deploy/mysite/onbook;
listen 80;
server_name onbook.me;
location / {
root $htdocs;
try_files $uri $uri/ /index.html =404;
}
}
这样,静态网站就搞定了,没有麻烦的#号了,可以直接访问和任意页面的刷新。
动态网站,我们同样需要修改的地方包括3个文件。
index.html : ng-app的定义文件
app.js : 对应ng-app的控制文件
server.js : Express框架的路由访问控制文件
index.html 和 app.js两个文件修改,同静态网站的解决方案。动态网站,一般不是通过Nginx直接路由,而是通过Web服务器管理路由。假设我们使用的是Node.js的Express的Web框架。
打开Express框架的路由访问控制文件server.js,增加路由配置。
app.use(function (req, res) {
console.log(req.path);
if(req.path.indexOf('/api')>=0){
res.send("server text");
}else{ //angular启动页
res.sendfile('app/index.html');
}
});
设置当 站内路径(req.path) 不包括 /api 时,都转发到 AngularJS的ng-app(index.html)。所以,我们再直接访问地址 (http://onbook.me/book)时,/book 不包括 /api,就会被直接转发到AngularJS进行路由管理。我们就实现了路由的优化!
因为AngularJs的特性(or 浏览器本身的缓存?),angular默认的HTML模板加载都会被缓存起来。导致每次修改完模板之后都得经常需要清除浏览器的缓存来保证浏览器去获得最新的html模板,自己测试还好,但如果更新了服务器的模板内容,用户可不会每个都配合你去清除浏览器的缓存。故这还真是个大问题。
app.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId/ch/', {
templateUrl: 'chapter.html',
controller: 'ChapterController'
});
});
方法一:在模板文件路径后加时间戳(or 其他随机数),强制AngularJs每次从服务器加载新的模板,不过这种方法太不美观了。
<pre name="code" class="javascript">
app.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId/ch/', {
templateUrl: 'chapter.html' + '?datestamp=' + (new Date()).getTime(),
controller: 'ChapterController'
});
});
方法二:使用$templateCache清除缓存
// 禁止模板缓存
app.run(function($rootScope, $templateCache) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (typeof(current) !== 'undefined'){
$templateCache.remove(current.templateUrl);
}
});
});
在配置 路由地址后,即在app.config之后添加这段代码,可禁止AngularJs将templateUrl缓存起来。
方法三:在state provider中禁用缓存
$stateProvider.state('myState', {
cache: false,
url : '/myUrl',
templateUrl : 'my-template.html'
})
方法四:在标签中禁用缓存
<ion-view cache-view="false" view-title="My Title!">
...
</ion-view>
方法五:在html设置不缓存的header
var app = angular.module('phonecat', ['ngRoute']);
app.config(['$routeProvider', '$httpProvider',
function($routeProvider, $httpProvider) {
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
$httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
$routeProvider.
when('/phones', {
templateUrl: 'phone-list.html',
controller: PhoneListCtrl
}).
when('/phones/:phoneId', {
templateUrl: 'phone-detail.html',
controller: PhoneDetailCtrl
}).
otherwise({
redirectTo: '/phones'
});
}
]);
<div ng-controller="PlayerController"> {{ programs }} </div>
依赖注入(DI)的好处不再赘言,使用过spring框架的都知道。angularjs作为前台js框架,也提供了对DI的支持,这是javascript/jquery不具备的特性。angularjs中与DI相关有angular.module()、angular.injector()、 injector、 provide。对于一个DI容器来说,必须具备3个要素:服务的注册、依赖关系的声明、对象的获取。比如spring中,服务的注册是通过xml配置文件的标签或是注解@Repository、@Service、@Controller、@Component实现的;对象的获取可以ApplicationContext.getBean()实现;依赖关系的声明,即可以在xml文件中配置,也可以使用@Resource等注解在java代码中声明。在angular中,module和$provide相当于是服务的注册;injector用来获取对象(angular会自动完成依赖的注入);依赖关系的声明在angular中有3种方式。下面从这3个方面,介绍下angular的DI。
1、angular.module()创建、获取、注册angular中的模块
The angular.module() is a global place for creating, registering and retrieving Angular modules.When passed two or more arguments, a new module is created. If passed only one argument, an existing module (the name passed as the first argument to module) is retrieved。
// 传递参数不止一个,代表新建模块;空数组代表该模块不依赖其他模块
var createModule = angular.module("myModule", []);
// 只有一个参数(模块名),代表获取模块
// 如果模块不存在,angular框架会抛异常
var getModule = angular.module("myModule");
// true,都是同一个模块
alert(createModule == getModule);
该函数既可以创建新的模块,也可以获取已有模块,是创建还是获取,通过参数的个数来区分。
angular.module(name, [requires], [configFn]);
name:字符串类型,代表模块的名称;
requires:字符串的数组,代表该模块依赖的其他模块列表,如果不依赖其他模块,用空数组即可;
configFn:用来对该模块进行一些配置。
现在我们知道如何创建、获取模块了,那么模块究竟是什么呢?官方的Developer Guide上只有一句话:You can think of a module as a container for the different parts of your app – controllers, services, filters, directives, etc.现在我还不太理解,大致就是说模块是一些功能的集合,如控制器、服务、过滤器、指令等子元素组成的整体。现在解释不了,先遗留。
2、$provide和模块的关系
The $provide service has a number of methods for registering components with the $injector. Many of these functions are also exposed on angular.Module.
之前提到过:module和provide是用来注册服务到injector中的。查看官方的API,可以看到$provide提供了provide()、constant()、value()、factory()、service()来创建各种不同性质的服务;angular.Module中也提供了这5个服务注册方法。其实2者功能是完全一样的,就是用来向DI容器注册服务到injector中。
官方API下的auto有$provide 和 $injector
,Implicit module which gets automatically added to each $injector
.按照字面意思是说,每一个injector都有这2个隐含的服务。但1.2.25版本中,感觉没有办法获取injector中的$provide
。不知道这是为什么?一般来说也不需要显示使用这个服务,直接使用module中提供的API即可。
var injector = angular.injector();
alert(injector.has("$provide"));//false
alert(injector.has("$injector"));//true
3、angular.injector()
使用angular.injector();也能获取到注入器,但是没有和模块绑定。这种做法是没有意义的,相当于是你创建了一个空的DI容器,里面都没有服务别人怎么用呢。正确的做法是,在创建注入器的时候,指定需要加载的模块。
// 创建myModule模块、注册服务
var myModule = angular.module('myModule', []);
myModule.service('myService', function() {
this.my = 0;
});
// 创建herModule模块、注册服务
var herModule = angular.module('herModule', []);
herModule.service('herService', function() {
this.her = 1;
});
// 加载了2个模块中的服务
var injector = angular.injector(["myModule","herModule"]);
alert(injector.get("myService").my);
alert(injector.get("herService").her);
如果加载了多个模块,那么通过返回的injector可以获取到多个模块下的服务。这个例子中如果只加载了myMoudle,那么得到的injector就不能访问herMoudle下的服务。这里特别需要注意下:angular.injector()可以调用多次,每次都返回新建的injector对象。
var injector1 = angular.injector(["myModule","herModule"]);
var injector2 = angular.injector(["myModule","herModule"]);
alert(injector1 == injector2);//false
4、angular中三种声明依赖的方式
angular提供了3种获取依赖的方式:inference、annotation、inline方式。
// 创建myModule模块、注册服务
var myModule = angular.module('myModule', []);
myModule.service('myService', function() {
this.my = 0;
});
// 获取injector
var injector = angular.injector(["myModule"]);
// 第一种inference
injector.invoke(function(myService){alert(myService.my);});
// 第二种annotation
function explicit(serviceA) {alert(serviceA.my);};
explicit.$inject = ['myService'];
injector.invoke(explicit);
// 第三种inline
injector.invoke(['myService', function(serviceA){alert(serviceA.my);}]);
其中annotation和inline方式,对于函数参数名称没有要求,是推荐的做法;inference方式强制要求参数名称和服务名称一致,如果JS代码经过压缩或者混淆,那么功能会出问题,不建议使用这种方式。
$injector
、$rootScope
和$scope
的概念和关联关系$injector、$rootScope和$scope
是angularJS框架中比较重要的东西,理清它们之间的关系,对我们后续学习和理解angularJS框架都非常有用。
1、$injector
其实是一个IOC容器,包含了很多服务(类似于spring框架中的bean),其它代码能够通过 $injector.get(“serviceName”)的方式,从injector中获取所需要的服务。详情参考这篇文章
2、scope是angularJS中的作用域(其实就是存储数据的地方),很类似javascript的原型链。搜索的时候,优先找自己的scope,如果没有找到就沿着作用域链向上搜索,直至到达根作用域rootScope。
3、$rootScope
是由angularJS加载模块的时候自动创建的,每个模块只会有1个rootScope。rootScope创建好会以服务的形式加入到$injector
中。也就是说通过$injector.
get(“$rootScope
“);能够获取到某个模块的根作用域。更准确的来说,$rootScope是由angularJS的核心模块ng创建的。
示例1:
// 新建一个模块
var module = angular.module("app",[]);
// true说明$rootScope确实以服务的形式包含在模块的injector中
var hasNgInjector = angular.injector(['app','ng']);
console.log("has $rootScope=" + hasNgInjector.has("$rootScope"));//true
// 获取模块相应的injector对象,不获取ng模块中的服务
// 不依赖于ng模块,无法获取$rootScope服务
var noNgInjector = angular.injector(['app']);
console.log("no $rootScope=" + noNgInjector.has("$rootScope"));//false
// 获取angular核心的ng模块
var ngInjector = angular.injector(['ng']);
console.log("ng $rootScope=" + ngInjector.has("$rootScope"));//true
上面的代码的确可以说明:$rootScope
的确是由核心模块ng创建的,并以服务的形式存在于injector中。
如果创建injector的时候,指定了ng模块,那么该injector中就会包含$rootScope
服务;否则就不包含$rootScope
。
示例2:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <script src="angular-1.2.25.js"></script> <script> var module = angular.module("app",[]); // 控制器里的$injector,是由angular框架自动创建的 function FirstController($scope,$injector,$rootScope) { $rootScope.name="aty"; } //自己创建了个injector,依赖于app和ng模块 var myInjector = angular.injector(["app","ng"]); var rootScope = myInjector.get("$rootScope"); alert(rootScope.name);//udefined </script> </head> <body ng-app="app"> <div id="first" ng-controller="FirstController"> <input type="text" ng-model="name"> <br> {{name}} </div> </body> </html>
angular.injector()可以调用多次,每次都返回新建的injector对象。所以我们自己创建的myInjector和angular自动创建的$injector不是同一个对象,那么得到的rootScope也就不是同一个。更详细的可以看angular.injector()章节。
示例3:
<!doctype html> <html lang="en"> <head> <script src="angular-1.2.25.js"></script> <script> function FirstController($scope,$injector,$rootScope) { // true console.log("scope parent :" + ($scope.$parent ==$rootScope)); } </script> </head> <body ng-app> <div id="first" ng-controller="FirstController"> <input type="text" ng-model="name"> <br> {{name}} </div> </body> </html>
ng-controller指令给所在的DOM元素创建了一个新的 scope对象,并作为rootScope的子作用域。 scope是由 rootScope创建的, scope不会包含在$injector中。
示例4:
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>scope()</title> <script src="jquery-1.11.1.js"></script> <script src="angular-1.2.25.js"></script> <script> //记住rootScope,用来判断跨控制器是否相等 var first_rootScope = null; //记住scope,用来判断跨控制器是否相等 var first_scope = null; //记住injector,用来判断跨控制器是否相等 var first_injectot = null; // 第1个angular控制器 function FirstController($scope,$injector,$rootScope) { $rootScope.name = "aty"; first_rootScope = $rootScope; first_injectot = $injector; first_scope = $scope; } // 第2个angular控制器,主要是来测试跨controller时injector和scope的表现 function SecondController($scope,$injector,$rootScope) { console.log("first_rootScope==second_rootScope:" + (first_rootScope==$rootScope));//true console.log("first_injectot==second_injector:" + (first_injectot==$injector));//true console.log("first_scope==second_scope:" + (first_scope==$scope));//false } </script> </head> <body ng-app> <div id="first" ng-controller="FirstController"> <input type="text" ng-model="name"> <br> <div id="tips"></div> </div> <h2>outside of controller</h2> <br> <!--访问每一个应用(模块)的rootScope--> {{$root.name}} <div id="noControllerDiv"/> <div ng-controller="SecondController"> </div> </body> </html>
ng-app定义了一个angular模块,每个模块只有一个 rootScope,只有一个 injector,但可以有多个$scope。
弄清了 injector、 rootScope和$scope这3者之间的关系,我们看下angular提供的2个API,一个是scope(),一个是injector()。使用angular.element()返回的DOM对象,都会包含这2个方法,用来获取与之关联的scope和injector。
由于每个模块的injector是唯一的,所以angular.element().injector()直接返回元素所在模块的injector。
angular.element().scope()可以获取到当前元素的scope或父scope。如果当前元素有scope,则返回自己的scope;如果没有则向父亲方向寻找,如果找不到返回rootScope。即返回作用域链上,距离该元素最近的scope。
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>scope()</title> <script src="jquery-1.11.1.js"></script> <script src="angular-1.2.25.js"></script> <script> function FirstController($scope,$injector,$rootScope) { //获取body对象 var domBody = document.getElementsByTagName('body')[0]; // 通过ng-app指令所在的DOM元素获取rootScope var rtScope = angular.element(domBody).scope(); //当前元素没有新作用域,获取父作用域即rootScope var noScope = angular.element("#noControllerDiv").scope(); // true console.log("rtScope==noScope:" + (rtScope==noScope)); //ng-controller所在的元素,返回的scope var scopeOnController = angular.element("#first").scope(); // ng-controller内部的元素返回所在的scope var inController = angular.element("#tips").scope(); //true console.log("scopeOnController==inController:" + (scopeOnController==inController)); //验证通过DOM获取的scope是否与注入的$scope和$rootScope一致 //true console.log("result1:" + (rtScope==$rootScope)); //true console.log("result2:" + (inController==$scope)); } </script> </head> <body ng-app> <div id="first" ng-controller="FirstController"> <input type="text" ng-model="name"> <br> <div id="tips"></div> </div> <h2>outside of controller</h2> <br> <!--访问每一个应用(模块)的rootScope--> {{$root.name}} <div id="noControllerDiv"/> </body> </html>
目录
1.URL的#号问题
使用AngularJS的朋友都应该了解,AngularJS框架定义了自己的前端路由控制器,通过不同URL实现单面(ng-app)对视图(ng-view)的部署刷新,并支持HTML5的历史记录功能,详细介绍可以参考文章:AngularJS路由和模板。
对于默认的情况,是不启动HTML5模式的,URL中会包括一个#号,用来区别是AngularJS管理的路径还是WebServer管理的路径。
比如:下面的带#号的URL,是AngularJS管理的路径。
http://onbook.me/
http://onbook.me/#/
http://onbook.me/#/book
http://onbook.me/#/about
这种体验其实是不太友好的,特别是像我这种喜欢简洁设计的人,#号的出现非我自愿的,怎么看怎么难受。AngularJS框架提供了一种HTML5模式的路由,可以直接去掉#号。
通过设置$locationProvider.html5Mode(true)就行了。
book.config([‘ routeProvider′,′ locationProvider’, function ( routeProvider, locationProvider) {
//..省略代码
$locationProvider.html5Mode(true);
}]);
支持HTML5的路由URL。
http://onbook.me/
http://onbook.me/book
http://onbook.me/about
接下来的问题就来了,当用这种方式设置了路径以后。如果用户从首页(http://onbook.me/)开始访问,然后跳转到 图书页(http://onbook.me/book)一切正常。但如果用户直接打开 图书页(http://onbook.me/book) ,就会出现404错误。
url1
就是这个问题纠结了我好长时间,让我不得不用带#号的URL。
- 找到错误原因
那么,这个问题的原因出在哪里了呢? 在路径解析上出错了。
让我从头说起,AngularJS是单页应用,一个ng-app对应一个页面,一个URL。AngularJS实现了自己的前端路由,让一个ng-app可以管理多个URL,再对应到多个ng-vew上面。当我们去访问URL(http://onbook.me/book) 的时候,怎么确定这个路径是 WebServer 后台管理的URL还是AngularJS前台管理的URL呢?
分2种情况看:
实现起来分为2种解决方案:
1. 静态网站:纯前台网站(JS+HTML+CSS),通过Nginx提供Web服务。
2. 动态网站:前台(JS + HTML + CSS) + 后台Node.js提供Web服务。
静态网站,我们需要修改的地方包括3个文件
index.html : ng-app的定义文件
app.js : 对应ng-app的控制文件
nginx.conf : nginx的网站配置文件
编辑 index.html,增加base标签。
<html lang="zh-CN" ng-app="book">
<head>
<base href="/">
// 省略代码
编辑app.js,增加 $locationProvider.html5Mode(true);
book.config(['$routeProvider', '$locationProvider', '$sceProvider', 'tplProvider', function ($routeProvider, $locationProvider, $sceProvider, tplProvider) {
$routeProvider
.when('/', {templateUrl: tplProvider.html('welcome'), controller: 'WelcomeCtrl'})
.when('/book', {templateUrl: tplProvider.html('book'), controller: 'BookCtrl'}) //图书
.when('/book-r1', {templateUrl: tplProvider.html('book-r1'), controller: 'BookR1Ctrl'}) //R的极客理想
.when('/video', {templateUrl: tplProvider.html('video'), controller: 'VideoCtrl'}) //视频
.when('/about', {templateUrl: tplProvider.html('about'), controller: 'AboutCtrl'}) //关于作者
.otherwise({redirectTo: '/'});
$locationProvider.html5Mode(true);
}]);
编辑nginx的配置文件,增加try_files配置。
server {
set $htdocs /www/deploy/mysite/onbook;
listen 80;
server_name onbook.me;
location / {
root $htdocs;
try_files $uri $uri/ /index.html =404;
}
}
这样,静态网站就搞定了,没有麻烦的#号了,可以直接访问和任意页面的刷新。
动态网站,我们同样需要修改的地方包括3个文件。
index.html : ng-app的定义文件
app.js : 对应ng-app的控制文件
server.js : Express框架的路由访问控制文件
index.html 和 app.js两个文件修改,同静态网站的解决方案。动态网站,一般不是通过Nginx直接路由,而是通过Web服务器管理路由。假设我们使用的是Node.js的Express的Web框架。
打开Express框架的路由访问控制文件server.js,增加路由配置。
app.use(function (req, res) {
console.log(req.path);
if(req.path.indexOf('/api')>=0){
res.send("server text");
}else{ //angular启动页
res.sendfile('app/index.html');
}
});
设置当 站内路径(req.path) 不包括 /api 时,都转发到 AngularJS的ng-app(index.html)。所以,我们再直接访问地址 (http://onbook.me/book)时,/book 不包括 /api,就会被直接转发到AngularJS进行路由管理。我们就实现了路由的优化!
因为AngularJs的特性(or 浏览器本身的缓存?),angular默认的HTML模板加载都会被缓存起来。导致每次修改完模板之后都得经常需要清除浏览器的缓存来保证浏览器去获得最新的html模板,自己测试还好,但如果更新了服务器的模板内容,用户可不会每个都配合你去清除浏览器的缓存。故这还真是个大问题。
app.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId/ch/', {
templateUrl: 'chapter.html',
controller: 'ChapterController'
});
});
方法一:在模板文件路径后加时间戳(or 其他随机数),强制AngularJs每次从服务器加载新的模板,不过这种方法太不美观了。
<pre name="code" class="javascript">
app.config(function($routeProvider, $locationProvider) {
$routeProvider
.when('/Book/:bookId/ch/', {
templateUrl: 'chapter.html' + '?datestamp=' + (new Date()).getTime(),
controller: 'ChapterController'
});
});
方法二:使用$templateCache清除缓存
// 禁止模板缓存
app.run(function($rootScope, $templateCache) {
$rootScope.$on('$routeChangeStart', function(event, next, current) {
if (typeof(current) !== 'undefined'){
$templateCache.remove(current.templateUrl);
}
});
});
在配置 路由地址后,即在app.config之后添加这段代码,可禁止AngularJs将templateUrl缓存起来。
方法三:在state provider中禁用缓存
$stateProvider.state('myState', {
cache: false,
url : '/myUrl',
templateUrl : 'my-template.html'
})
方法四:在标签中禁用缓存
<ion-view cache-view="false" view-title="My Title!">
...
</ion-view>
方法五:在html设置不缓存的header
var app = angular.module('phonecat', ['ngRoute']);
app.config(['$routeProvider', '$httpProvider',
function($routeProvider, $httpProvider) {
if (!$httpProvider.defaults.headers.get) {
$httpProvider.defaults.headers.get = {};
}
$httpProvider.defaults.headers.common["X-Requested-With"] = 'XMLHttpRequest';
$httpProvider.defaults.headers.get['Cache-Control'] = 'no-cache';
$httpProvider.defaults.headers.get['Pragma'] = 'no-cache';
$routeProvider.
when('/phones', {
templateUrl: 'phone-list.html',
controller: PhoneListCtrl
}).
when('/phones/:phoneId', {
templateUrl: 'phone-detail.html',
controller: PhoneDetailCtrl
}).
otherwise({
redirectTo: '/phones'
});
}
]);