<button ng-click="show=true">show</button> <dialog title="Hello {{username}}." visible="show" on-cancel="show = false" on-ok="show = false; doSomething()"> Body goes here: {{username}} is {{title}}. </dialog>
点击“show”按钮将会打开dialog。dialog有一个标题,与数据“username”绑定,还有一段我们想放置在dialog内部的内容。
下面是一段为了dialog而编写的模版定义:
<div ng-show="show()"> <h3>{{title}}</h3> <div ng-transclude></div> <div> <button ng-click="onOk()">Save changes</button> <button ng-click="onCancel()">Close</button> </div> </div>
这将无法正确渲染,除非我们对scope做一些特殊处理。
第一个我们需要解决的问题是,dialog模版期望title会被定义,而在初始化时会与username绑定。此外,按钮需要onOk、onCancel两个function出现在scope里面。这限制了插件的效能(this limits the usefulness of the widget。。。)。为了解决映射问题,通过如下的本地方法(locals,估计在说directive定义模版中的scope)去创建模版期待的本地变量:
scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility. }
在控件scope中创建本地属性,带来了两个问题:
1. isolation(属性隔离?) - 如果用户忘记了在控件模版设置title这个元素属性,那么title将会与祖先scope的属性”title”(如有)绑定。这是不可预知、不可取的。
2. transclusion - transcluded DOM可以查看控件的locals(isolate scope?),locals会覆盖transclusion中真正需要绑定的属性。在我们的例子中,插件中的title属性破坏了transclusion的title属性。
为了解决这个缺乏属性隔离的问题,我们需要为这个directive定义一个isolated scope。isoloted scope不是通过从child scope(这里为啥是child scope?不是parent scope吗?)原型继承的,所以我们无需担心属性冲突问题(作为当前scope的兄弟)。
然而,isolated scope带来了一个新问题:如果transcluded DOM 是控件的isolated scope的一个子元素,那么他将不能与任何东西绑定(if a transcluded DOM is a child of the widget isolated scope then it will not be able to bind to anything)。因此,transcluded scope是原始scope的一个子scope,在控件为本地属性而创建isolated scope之前创建的。transcluded与控件isolated scope属于兄弟节点(scope树中)。
这也许看起来会有点意想不到的复杂,但这样做让控件用户与控件开发者至少带来了惊喜。(解决了问题)
因此,最终的directive定义大致如下:
transclude:true, scope :{ title: 'bind', // set up title to accept data-binding onOk: 'expression', // create a delegate onOk function onCancel: 'expression', // create a delegate onCancel function show: 'accessor' // create a getter/setter function for visibility. //我试过这么整,失败……请继续往下看 }
我尝试把上面的代码拼凑成一个完整的例子。直接复制的话,无法达到预期效果。但稍作修改后,插件可以运行了。
<!DOCTYPE html> <html ng-app="Dialog"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"/> <title>directive-dialog</title> <meta content="IE=edge,chrome=1" http-equiv="X-UA-Compatible"> <script src="https://www.jb51.net/angular.js" type="text/javascript"></script> </head> <body> <div ng-controller="MyCtrl"> <button ng-click="show=true">show</button> <dialog title="Hello {{username}}" visible="{{show}}" on-cancel="show=false;" on-ok="show=false;methodInParentScope();"> <!--上面的on-cancel、on-ok,是在directive的isoloate scope中通过&引用的。如果表达式中包含函数,那么需要将函数绑定在parent scope(当前是MyCtrl的scope)中--> Body goes here: username:{{username}} , title:{{title}}. <ul> <!--这里还可以这么玩~names是parent scope的--> <li ng-repeat="name in names">{{name}}</li> </ul> </dialog> </div> <script type="text/javascript"> var myModule = angular.module("Dialog", []); myModule.controller("MyCtrl", function ($scope) { $scope.names = ["name1", "name2", "name3"]; $scope.show = false; $scope.username = "Lcllao"; $scope.title = "parent title"; $scope.methodInParentScope = function() { alert("记住。。scope里面通过&定义的东东,是在父scope中玩的!!。。。"); }; }); myModule.directive('dialog', function factory() { return { priority:100, template:['<div ng-show="visible">', ' <h3>{{title}}</h3>', ' <div ng-transclude></div>', ' <div>', ' <button ng-click="onOk()">OK</button>', ' <button ng-click="onCancel()">Close</button>', ' </div>', '</div>'].join(""), replace:false, transclude: true, restrict:'E', scope:{ title:"@",//引用dialog标签title属性的值 onOk:"&",//以wrapper function形式引用dialog标签的on-ok属性的内容 onCancel:"&",//以wrapper function形式引用dialog标签的on-cancel属性的内容 visible:"@"//引用dialog标签visible属性的值 } }; }); </script> </body> </html>
十三、Creating Components