##angular使用iframe方法
主要利用$sce服务:
html:
iframe(name="iframe",width="100%",height="100%",frameborder="0",scrolling="auto",ng-src="{{trustSrc()}}")
js:
.controller('mainController', [
'$scope', "$sce"
($scope, $sce)->
$scope.frameSrc = "http://www.baidu.com/"
#iframe解析src
$scope.trustSrc = ()->
$sce.trustAsResourceUrl($scope.frameSrc)
])
##angular双向数据绑定的原理
angular的双向数据绑定采用的是脏值检测,而不是重写setter和getter方法,主要利用的是$digest和$watch服务。
angular把要检测的模型配有各自的watcher,watchers把旧的值备份一副本。angular对常用的DOM事件、ajax事件等做了封装,在里面触发进入digest流程。在digest流程中,digest从rootScope开始遍历所有要检测的模型,当检测到新的值就打个dirty的标记,触发该watcher的listener,这个listener包括angular内置的更新数据和视图操作以及用户自定义的操作(如果有的话),然后再次循环digest,直到不再有变化为止。
总结起来一句话就是,通过angular指定的方式来触发angular对数据模型进行检测,如果检测到有变化则进行更新操作。
angular封装好的事件包括:
- DOM事件,ng-click等
- ajax事件,$http
- Location变更事件,$location
- Timer事件,$timeout, $interval,
- 执行$digest,$apply
也就是说,要么使用angular原生的指令和服务,要么使用$digest或$apply去通知angular进行digest。
脏值检测、双向数据绑定
如何才能知道Model发生了变化:脏值检测$watch与$digest
angular之所以实现双向数据绑定,而其他MVC框架没有,主要因为:
- 如果一个表单里有大量的元素,要是用jQuery的话,会有大量的代码去获取该DOM元素的值,然后去检验,再设置到model里,而angular则帮我们实现了这个烦琐的步骤,省了大量的代码
- angular实现的双向数据绑定,主要是因为有大难点:「脏值检测」,「振荡问题」 所谓「振荡问题」是比如更改表单的值,这时表单的值的改变会同步更新到数据模型,而数据模型更新后,又要去刷新视图,造成了「依赖循环问题」
##指令exports API
对于指令向外exports API这个问题,我专门请教了一些专家,他们也给出了专业的回答:
我的问题是:
Sometimes I want to make custom directives export API in order to let parent controller update the directive ui.
然后@gkalpak 大神回答:
@luozt, there are several ways to implement this. Using events is one. Other ways include using a shared service or using directive communication (i.e. requiring a parent controller and calling functions on it). What to choose strongly depends on the exact usecase.
所以,有三种方法可以实现:1、通过$scope的$on, $emit, $broadcast事件;2、一个共享的service;3、指令内的交互如link的第4个参数parentController
我选择的是第一种events的方法,因为它能自动卸载。但要注意的是:需要给指令指定一个ID,在事件里判断触发事件的参数是否有该ID,因为同一页面可能同时存在多个同一指令,所以要用ID去区分。示例如下:
#bootstrap modal的通用方法,.modal指令
#包括$modal.modal("show")
#外部controller使用
.directive("modal", [
()->
return {
restrict: "C"
scope: {}
link: (scope, elem, attrs)->
#exports API
scope.$on("api.bsModal", (event, data)->
if data.drId != attrs.drId
return
switch data.apiName
when "show"
elem.modal("show")
when "hide"
elem.modal("hide")
)
}
])
另外,由徐飞撰写的文章也提及到组件跟主文档的通讯, WebComponents文章得知,ng的指令通信都是用事件的方式来进行组件内外通信!!
##$templateCache缓存模板
app.run(function($templateCache){
$templateCache.put("hello.html", "<div>what is app</div>")
})
app.directive("hello", function($templateCache){
return {
restrict: "AECM",
template: $templateCache.get("hello.html"),
replace: true
}
})
##scope变量直接插入HTML的方法
方法1:ng-bind-html
- 引入angular-sanitize.js
- 注入
ngSanitize
模块 - 使用
ng-bind-html
即可绑定插入HTML:<div ng-bind-html="html"></div>
方法2:$sce服务
好处是无需引入额外的插件:
HTML:
<div ng-bind-html="get()"></div>
JS:
app.controller("control", function($scope, $sce){
$scope.get = function(){
return $sce.trustAsHtml(html);
};
});
##触发$scope的destroy事件 ##$timeout的使用
angular切换视图时,会触发$scope
的destroy
事件
//当timeout被定义时,它返回一个promise对象
var timer = $timeout(
function() {
console.log("Timeout executed", Date.now());
},
2000
);
//将resolve/cancel处理函数绑定到timer promise
timer.then(
function() {
console.log("Timer resolved!", Date.now());
},
function() {
console.log("Timer canceled!", Date.now());
}
);
//当DOM元素从页面中被移除时,AngularJS将会在scope中触发$destory事件。这让我们可以有机会来cancel任何潜在的定时器
$scope.$on(
"$destroy",
function(event) {
$timeout.cancel(timer);
}
);
##指令的scope绑定策略
@
绑定:传递字符串的值,对应的是字符串=
绑定:双向数据绑定,对应的是一个变量&
负责调用方法,对应的是一个函数的调用
jade部分:
div(ng-controller="MyCtrl")
| Ctrl:
input(type="text",ng-model="ctrlFlavor")
| Directive:
drink(flavor="ctrlFlavor", taste="{{ctrlFlavor}}", pour="fnPour(thing)")
javascript部分:
app.controller("MyCtrl", function($scope){
$scope.ctrlFlavor = "百事可乐"
$scope.fnPour = function(thing){
console.log(thing)
}
})
app.directive("drink", function(){
return {
restrict: "AE",
scope: {
// 实现的是字符串传递
taste: "@",
// 实现数据双向绑定
// flavor属性上的ctrlFlavor,相互传递
// flavor = ctrlFlavor
flavor: "=",
// =号也可这样写
title: "=expanderTitle",
// 方法调用
pour: "&"
},
template: 'Taste: {{taste}}, <input type="text" ng-model="flavor" />'+
// pour方法传进去的是一个对象:thing绑定的是flavor
'<button ng-click="pour({thing:flavor})">提交</button>'
}
})
##指令compile函数
- 一般很少用compile函数
- compile函数是用于对指令本身的模板进行转换
- link作用是在模型和视图之间建立关联,包括在DOM上注册监听
- compile函数和link函数不会同时出现。如果同时出现的话,link函数会无效,angular会执行的是compile函数返回的link函数
- 对于同一指令的多个实例,compile函数只会执行一次,而link函数对每个实例都会执行一次
- 最重要的一点是,compile函数必须返回link函数,这是angular的规定
html:
<div alotofhello="5">
<p>hello, boy!</p>
</div>
js:
myApp.directive("alotofhello", function() {
return {
restrict: "A",
compile: function(el, attrs, transclude) {
// 对标签进行一些变换
var tpl = el.children().clone()
for (var i = 0; i < attrs.alotofhello - 1; i++) {
el.append(tpl.clone())
}
return function($scope, element, attr, parentController) {
//必须返回这个link函数,如果外部再定义了link函数,则外部的无效,以这个为准
}
}
}
})
##指令写法:横杠和驼峰
指令写法:属性如果要对单词进行拆分,不要用驼峰式,要用横杠拆分,而在directive里用驼峰的方式传递,如:<hello some-attr>
对应指令独立scope的scope:{someAttr:"="}
,data-前缀的属性指令需去掉data-才匹配
指令的ng-transclude使用
指令的controller
指令的require函数
指令的scope.$apply方法调用
HTML指令:
<hello>
<p>这是指令里面的内容</p>
</hello>
JS:
app.directive("hello", function(){
return {
// 独立作用域,使每个指令都有自己的$scope
// 如果去掉,那么指令内部封装的方法会同时封装在外部controller的$scope
// 除非外面在调用指令时也用到了ng-if等指令,它会封闭指令的scope
scope: {},
restrict: "AECM",
template: "<div>what's app: <div ng-transclude></div></div>",
transclude: true,
// 暴露一些方法出去给公共调用
// 公共调用时须先使用require: "^hello"
// 然后在link的第4个参数parentController调用
// parentController.addSpeed()
controller: function($scope){
$scope.abilities = []
// 使用this来暴露出去
this.addSpeed = function(){
$scope.abilities.push("speed")
}
this.addStrength = function(){
$scope.abilities.push("strength")
}
},
link: function($scope, element, attr, parentController){
// element是jQuery封装后的DOM
element.addClass("btn btn-primary")
element.bind("click", function(){
console.log(123)
})
// 调用方式1
$scope.loadData();
// 调用方式2,注意里面字符串要加()
$scope.$apply("loadData()")
// 调用方式3,调用DOM的attr,不用加()
// 在attr不管是大写还是小写,在调用时一律都用小写
$scope.$apply(attr.howtoload)
}
}
})
app.directive("world", function(){
return {
// 表明依赖于hello指令
require: "^hello",
// 也有些在前面加?的
// require: "^?hello",
link: function($scope, element, attr, parentController){
parentController.addSpeed()
}
}
})
##支持RESTful的ngResource
比$http更强大和更灵活的服务,支持RESTful模式。注意需引入angular-resource.js插件
使用方法:
//引入模块依赖
var app = angular.module("myApp", ["ngResource"]);
app.controller("ctrl", ["$scope", "$resouce", function($scope, $resouce){
//可以定义参数在获取的url上
var obj = $resouce(":name.:type");
//也可在$resouce的函数参数中指定参数
// var obj = $resouce(":name.:type", {type:"json"});
//然后在这调用的参数中传入
$scope.data = obj.get({name:"result",type:"json"}, function(data){
console.log(data);
}, function(err){
console.log(err);
});
}]);
##自定义服务provider/service/factory
angular是个MVC框架。Provider相当于M层,controller相当于C层,而V层是在HTML中调用各种指令来实现
通过模块来自定义服务的方式:
var m1 = angular.module('myApp', []);
m1.provider('providerServices01', function() {
this.$get = function() {
return {
message: 'this is providerServices01'
}
}
});
// service方法可直接return返回值
m1.service('serviceServices01', function() {
return {
message: 'this is serviceServices01'
}
});
// service方法也可以直接通过this来定义方法
m1.service("serviceServices02", function(){
var _name = "kobe";
this.getName = function(){
return _name;
};
this.setName = function(name){
_name = name;
};
});
m1.controller('firstController', ['$scope', 'providerServices01', 'serviceServices01', function($scope, providerServices01, serviceServices01) {
console.log(providerServices01);
console.log(serviceServices01);
$scope.name = '张三';
}]);
通过在模块定义的回调函数中来自定义服务的方式:
var m1 = angular.module('myApp', [], function($provide) {
$provide.provider('providerServices01', function() {
this.$get = function() {
return 'this is providerServices01';
}
});
$provide.service('serviceServices01', function() {
return {
message: 'this is serviceServices01'
}
});
$provide.factory('factoryServices01', function() {
return {
message: 'this is factoryServices01'
};
});
});
m1.controller('firstController', ['$scope', 'providerServices01', 'factoryServices01', 'factoryServices02', 'serviceServices01', function($scope, providerServices01, factoryServices01, factoryServices02, serviceServices01) {
console.log(providerServices01);
console.log(factoryServices01);
console.log(factoryServices02);
console.log(serviceServices01);
$scope.name = '张三';
}]);
其中,service不能直接返回字符串等常量,须返回object的变量,而provider和factory可以直接返回字符串等常量。
Providers中唯一一种可以传进.config函数的服务,就是provider。这个作用是可以读取和更改在angularjs的配置。例如查看以下代码中的name的配置:
var myModule = angular.module("myApp", []);
myModule.config(function(providerServices01Provider){
providerServices01Provider.name = "Kobe";
});
myModule.provider("providerServices01", function(){
this.name = "";
this.$get = function(){
var _this = this;
return {
setName: function(newName){
_this.name = newName;
}
};
};
})
##filter
AngularJS内置了9个filter(参考文档):
currency, date, json, limitTo, lowercase, number, orderBy, uppercase
使用示例:
{{ 1304375948939 | date }}
{{ 1304375948939 | date: "MM/dd/yyyy @ h:mma" }}
{{ 1304375948939 | date: "yyyy-MM-dd hh:mm:ss" }}
自定义filter
HTML部分:
<div ng-controller="firstController">
{{name | customFiler:1:2}}
</div>
js部分:
app.controller("firstController", function($scope){
$scope.name = "hello, boys!";
});
app.filter("customFiler", function(){
return function(input,n1,n2){
console.log(input, n1, n2);
return input.replace(/hello/,"你好");
};
});
##ng-non-bindable
这个指令会使忽略其中的{{}}
:
<div ng-non-bindable>{{text}}</div>
##ng-value
<input value="{{text}}">
和<input ng-value="text">
是等价的
##$apply、$timeout、$watch
查看以下代码,以一个timeout后改变$scope里的值来查看它是否生效,来学习以上的方法:
myModule.controller("controllerName", ["$scope", "$timeout", function($scope, $setTimeout(function() {}, 10);){
$scope.name = "kobe";
//这个是不会生效
// setTimeout(function(){
// $scope.name = "t-mac";
// },1000);
// $apply方法:要这样才生效
setTimeout(function(){
$scope.$apply(function(){
$scope.name = "jordan";
});
},1000);
// $timeout方法:或者这样也生效
$timeout(function(){
$scope.age = "james";
},1000);
// $watch方法
// 少用$watch,不然内存开销太大
destroyWatch = $scope.$watch("name", function(newValue,oldValue){
if("james" === newValue){
return 1;
}else{
return 0;
}
});
// 卸载$watch
// destroyWatch();
}]);
angular的$digest cycle会在每次调用$scope.$apply或$scope.$digest的时候运行,$scope.$apply实际上是一个$rootScope的包装,它会从根$rootScope向下传播,而$scope.$digest只会在当前$scope中运行,并向下级$scope广播
$watch方法,第一个参数是返回需要监控模型的函数,如果传入字符串,angular还是会把它转换为函数,所以:
$scope.$watch("name", function(){});
//等同于
$scope.$watch(function(){return $scope.name;}, function(){});
##angular.bootstrap
动态的创建ng-app模块:
// 在HTML中不写ng-app
// 在JS中通过bootstrap方法来启动模块
angular.bootstrap(div1, ["myApp"]);
##Referencing DOM nodes in Angular expressions is disallowed!
因为CoffeeScript把默认最后一行会添加return,导致返回如jQueryDOM的元素,故在最后再增加一句return true
即可。详情可查看:http://stackoverflow.com/questions/23838497/coffeescript-referencing-dom-nodes-in-angular-expressions-is-disallowed