一篇文章看懂angularjs component组件
壹 ❀ 引
贰 ❀ 创建一个简单component
<body ng-controller="myCtrl"> <my-name></my-name> <you-name></you-name> </body>
angular.module('myApp',[]) .controller('myCtrl',function () {}) .component('myName',{ template:'<div>我的名字是听风是风</div>' }) .directive('youName',function (){ return{ restrict:'AE', replace:true, template:'<div>你的名字是陌生人</div>' } })
可以看到directive需要在注册名字后紧接回调函数,并在回调函数中返回一个包含directive配置的对象;而component简单很多,component名后面只用紧跟一个包含配置的对象即可,一个完全的component应该是这样:
angular.module('myApp', []) .controller('myCtrl', function () {}) .component('componentName', { template: 'String or Template Function', templateUrl:String, transclude:Boolean, bindings: {}, controllerAs:String, require:String, controller: function () {}, })
属性相比directive简直少了一大半,最直观的就是没了编译函数compile与链接函数link,下面我们一一介绍相关属性。
叁 ❀ 参数详解
1.template /ˈtempleɪt/ 模板
component的template用法与directive保持一致,将你需要渲染的DOM结构以字符串的形式拼接好作为template的值即可,比如在文章开头一个简单的例子就展示了template的用法。
但是有一点与directive不同,directive要求模板文件结构最外层必须使用一个根元素包裹(不管使用template还是templateUrl),但是component并没有这个要求,比如这样:
angular.module('myApp',[]) .controller('myCtrl',function () {}) .component('myName',{ template:'<div>我的名字是听风是风。</div><div>要做一个温柔的人。</div>' })
可以看到在template有两个同级的div元素,我并未用一个根元素包裹这两div也能正常显示,但如果是directive这样做就会报错:
.directive('youName',function (){ return{ restrict:'AE', replace:true, template:'<div>我的名字是听风是风。</div><div>要做一个温柔的人。</div>' } })
2.templateUrl 模板路径
component的templateUrl用法与directive用法一致,将模板的路径地址作为值赋予给templateUrl即可,比如这样:
angular.module('myApp',[]) .controller('myCtrl',function () {}) .component('myName',{ templateUrl:'../template/myName.html' })
需要注意的是加载模板需要服务器,否则会报错,这里给大家推荐一个本地服务器 live-server,用法很简单,大家可以看看。
3.transclude
在使用component时,如果组件中包含了其它DOM结构或者其它组件,你会发现组件解析后,原本的DOM直接消失不见了,看个例子:
<div ng-controller="myCtrl"> <my-name> <div>要做一个努力的人。</div> </my-name> </div>
angular.module('myApp', []) .controller('myCtrl', function () {}) .component('myName', { template: '<div >我是听风是风</div>' })
在组件my-name中原本还包裹了一个div元素,但是在模板解析后可以看到这个div直接被替换掉了,如果我们想保留这个div就得使用transclude属性,transclude一般与ng-transclude指令一起使用,看个例子:
angular.module('myApp', []) .controller('myCtrl', function () {}) .component('myName', { transclude: true, template: '<div >我是听风是风</div><div ng-transclude></div>' })
HTML结构不变,我们在组件中新增了 transclude:true,并在模板中新增了一个div元素,并为此div元素添加了ng-transclude指令,再看组件解析后就正常了,你会发现你想保留的div元素成了添加了ng-transclude指令元素的子元素。
如果我们要使用组件嵌套,这个属性也是必不可少,关于transclude就说到这。
4.controller
每个组件都拥有自己的controller控制器用于定义组件需要的数据方法等,component的controller值也可以是一个字符串或者一个函数,先说字符串的情况:
angular.module('myApp', []) .controller('myCtrl', function ($scope) { let vm = this; this.name = '时间跳跃'; $scope.age = 26; }) .component('myName', { transclude: true, template: '<div >我的名字是{{$ctrl.name}},我今年{{age}}了。</div>', controller:'myCtrl' })
神奇的是我在component中并未定义一个叫myCtrl的构造器,但是component还是解析了数据,这是因为当controller值为字符串时就会从应用中查找与字符串同名的构造函数作为自己的控制器函数,很明显父作用域控制器刚好也叫myCtrl,所以这就直接拿来用了。
这么做有个优点就是,component是默认自带隔离作用域的,也就是说父作用域的数据是无法通过继承传递给子组件,如果你组件自身并没有其它数据方法,通过这种办法倒是可以投机取巧一波。
当然controller我们一般的写法是后面接一个回调函数,像这样:
angular.module('myApp', []) .controller('myCtrl', function ($scope) {}) .component('myName', { transclude: true, template: '<div >我的名字是{{$ctrl.name}},我今年{{age}}了。</div>', controller: function ($scope){ let vm = this; this.name = '听风是风'; $scope.age = 18; } })
5.controllerAs
我们知道controller与view通信有两种方式,一是通过scope,将数据绑在scope上,视图中通过表达式解析即可渲染,二是通过this绑定,这里的controllerAs就是用来设置控制器的别名,controllerAs默认值为$ctrl,在上面的例子中已经有展示,我们再来看个例子:
angular.module('myApp', []) .controller('myCtrl', function ($scope) {}) .component('myName', { transclude: true, template: '<div >我的名字是{{vm.name}},我今年{{vm.age}}了。</div>', controllerAs: 'vm', controller: function ($scope) { let vm = this; this.name = '听风是风'; this.age = 18; } })
在上面的例子中,我们将controllerAs的值设置成vm,那么在模板中使用这个值时就是通过vm访问,如果大家对于scope与控制器controller的this有何区别存在疑惑,可以阅读博主这篇文章 angularjs $scope与this的区别,controller as vm有何含义?
6.bindings 父传值给子组件
还记得directive有一个scope属性可以决定directive是否创建隔离作用域,如果scope的值为对象,则表示指令创建隔离作用域,不再继承父作用域中的属性,父作用域想传值就得依赖绑定策略。
而component的bindings就是对应directive的scope:{}的情况,component默认创建隔离作用域,如果想使用父作用域的数据,就得使用bindings结合绑定策略,我们来看个例子:
<div ng-controller="myCtrl"> <my-name user-name="name" say-name="sayName"></my-name> </div>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { $scope.name = "听风是风"; $scope.sayName = function () { console.log($scope.name); }; }) .component('myName', { transclude: true, template: '<div >我的名字是{{vm.userName}}。</div><button ng-click="vm.sayName()">点我</button>', controllerAs: 'vm', bindings:{ userName:'<', sayName:'<' } })
在HTML中的组件上,我们以key-value的形式传值我们需要在组件中访问的属性和方法,注意key如果是多个单词建议使用 - 拼接,但在bindings中得改为小驼峰形式,这样我们就可以在模板中直接使用了。
在bindings中我们可以看到需要传值的数据后面跟了一个<符号,这是绑定策略的规则,<表示单项绑定,即数据传递给组件后,父作用域如果修改了数据,子会同步改变,但如果子修改不会修改父,看个例子:
<div ng-controller="myCtrl"> 我是父作用域:<input type="text" ng-model="name"><br> <my-name user-name="name" say-name="sayName"></my-name> </div>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { $scope.name = "听风是风"; }) .component('myName', { transclude: true, template: '我是组件:<input ng-model="vm.userName">', controllerAs: 'vm', bindings:{ userName:'<', } })
但如果我们将<改为 = 符号表示双向绑定,不管修改父还是子,双方都会同步更新,改成=之后是这样:
但需要注意的一点是,如果传递的数据是一个对象,由于浅拷贝的缘故,不管你用 = 还是<,如果修改了对象的属性,父子都会同步更新。
但是上面的例子是传递过来后直接给模板在使用,如果我想在组件的controller中使用怎么办呢,如果你尝试在控制器中打印传过来的值,你会发现是拿不到的,这里得借用钩子函数,比如onInit,我们来看个例子:
angular.module('myApp', []) .controller('myCtrl', function ($scope) { $scope.name = "听风是风"; }) .component('myName', { transclude: true, template: '我是组件:<input ng-model="vm.userName">', controllerAs: 'vm', bindings: { userName: '=', }, controller: function ($scope) { console.log(this.userName);// undefined console.log($scope.userName);// undefined this.$onInit = function () { console.log(this.userName);// 听风是风 console.log($scope.userName);// undefined }; } })
还是一样的传值,我分别在钩子函数内外打印了this.userName与$scope.userName,首先可以确定的是只能在钩子函数内访问到传递的值,那为什么this可以访问而$scope访问不到呢,因为component传值是绑定在控制器上的,所以只能通过this访问。
关于钩子函数我会单独利用一篇博客介绍,这里先挖坑,另外directive传值是绑定在scope上的,所以不需要$onInit你都能直接通过scope访问。
7.require 引用父级组件的控制器
我们知道directive指令的require属性能帮助当前指令引用父级指令中指令中控制器上的所有属性方法,当然component同样提供了这个属性,只是用法上有点区别。
directive的require值是一个字符串(引用单个)或者一个数组(引用多个),而component中require值是一个对象,写法上也有点区别,我们来看一个完整的例子:
<div ng-controller="myCtrl"> <her-name> <your-name> <my-name></my-name> </your-name> </her-name> </div>
angular.module('myApp', []) .controller('myCtrl', function ($scope) { $scope.name = "听风是风"; }) .directive('herName', function () { return { restrict: 'AE', replace: true, controller: function () { this.name = '伊莎贝拉'; } } }) .component('yourName', { controllerAs: 'vm', controller: function ($scope) { this.name = '时间跳跃'; } }) .component('myName', { template: '<div>她的名字是{{vm.she.name}},你的名字是{{vm.you.name}},我的名字是{{vm.myName}}.</div>', controllerAs: 'vm', require: { she: '?^herName', you: '?^yourName' }, controller: function () { this.$onInit = function () { console.log(this.she.name); console.log(this.you.name); }; this.myName = '听风是风'; } });
component的require有点bindings的意思,需要在组件内部指定一个变量来保存引用的组件控制器,通过例子我们也可以知道,require不仅可以引入组件还可以引入指令。
在Angular 1.5.6版本之后,如果require对象中key名和require的控制器同名,那么就可以省略控制器名,所以上面的require还可以简写成这样:
require: { herName: '^^', yourName: '^^' },
一个 ^ 表示从自己开始找,一直找到到上级组件,而^^表示直接从父级组件开始找,算是一个小技巧。
伍 ❀ 总
那么到这里component用法及属性就介绍完,说到底component就是阉割版的directive,用法上还是大同小异,directive怎么玩到了component还是一样,但从创建组件角度来说,component确实更简单更方便。
如果你要在组件编译阶段或者链接阶段做什么操作,或者说要操作DOM,component就无法满足你的需求,毕竟component未提供编译与链接函数,而且component默认只有使用element创建组件,并不支持属性类名或注释。
聊完了directive指令与component组件,你是否觉得这两兄弟看着相似却又有一些不同,如果你觉得让你有一些糊涂,可以阅读博主这篇文章 angularjs中directive指令与component组件有什么区别?。
那么本文到这里就真是结束了,希望对你有帮助。