一篇文章看懂angularjs component組件

 壹 ❀ 引

我在 angularjs 一篇文章看懂自定義指令directive 一文中詳細介紹了directive基本用法與完整屬性介紹。directive是個很神奇的存在,你可以不設置template屬性單純作爲操作DOM的函數,也就是俗稱的指令;你也可以帶上template讓其變成高度複用的組件。但在angularjs1.5版本之後我們可以使用component專門用於創建組件,當然你要堅持使用directive創建組件也沒有問題,那麼本文將帶你瞭解angularjs component,本文主要從基本用法,屬性介紹,組件傳值這幾個方法展開介紹,本文開始。

 貳 ❀ 創建一個簡單component

還記得怎麼創建directive嗎,component創建方式與directive大同小異,使用時組件名如果是my-name那麼聲明組件得使用小駝峯myName,看個簡單例子:
<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組件有什麼區別?

那麼本文到這裏就真是結束了,希望對你有幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章