听风是风

学或不学,知识都在那里,只增不减。

导航

一篇文章看懂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组件有什么区别?

那么本文到这里就真是结束了,希望对你有帮助。

posted on 2019-11-07 22:17  听风是风  阅读(10816)  评论(8编辑  收藏  举报