壹 ❀ 引
貳 ❀ 創建一個簡單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組件有什么區別?。
那么本文到這里就真是結束了,希望對你有幫助。