壹 ❀ 引

我在前面花了三篇文章用于介绍angularjs的指令directive,组件component,并专门花了一篇文章介绍directive与component的不同,其中提到在component的声明周期中需要配合钩子函数来实现组件部分功能,例如在bindings传值过程中,你得通过$onInit方法来初始化数据,那么我们就来好好聊聊component中常用的几个钩子函数,本文开始。

 贰 ❀ $onInit

在介绍component的文章中已经有涉及$onInit方法的说明,$onInit用于在component的controller中做数据初始化的操作。

常理上来说,即便我们不通过$onInit为组件绑定数据也是没问题的,看个简单的例子:

<div ng-controller="myCtrl">
<echo></echo>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.component('echo', {
template: '<div>{{vm.name}}</div><button ng-click="vm.sayName()">点我</button>',
controllerAs: 'vm',
controller: function () {
this.name = '听风是风';
this.sayName = function () {
console.log(this.name);
};
}
});

可如果我们需要使用bindings传递父作用域的数据,或者利用require注入上层组件的controller时,就一定得使用$onInit方法才能拿到传递过来的数据,来看个例子:

<div ng-controller="myCtrl">
<jack>
<echo my-name="{{name}}"></echo>
</jack>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.name = '时间跳跃';
})
.component('jack', {
controller: function () {
this.name = '听风是风';
}
})
.component('echo', {
controllerAs: 'vm',
require: {
jack: '^^'
},
bindings: {
myName: '@'
},
controller: function () {
console.log(this.myName); //undefined
console.log(this.jack); //undefined
this.$onInit = function () {
console.log(this.myName); //时间跳跃
console.log(this.jack); // controller {name: "听风是风"}
}
}
});

在上面的例子中我分别在父作用域上绑定了一个name属性,并通过bindings传递给组件echo,并注入了父组件jack的控制器,可以看到只有在$onInit中才能正确的拿到它们,这就是$onInit的作用。

 叁 ❀ $onChanges

事实上$onInit的初始化只会执行一遍,如果我们通过bindings传入了父作用域中的数据,父作用域的数据改变其实子组件是无法感知的,我们看个例子:

<div ng-controller="myCtrl">
<div>我的名字是{{myself.name}},我今年{{myself.age}}了</div>
<echo my-age="myself.age" my-name="myself.name"></echo>
<button ng-click="reduce()">一键返老还童</button>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.myself = {
name: '听风是风',
age: 26
};
$scope.reduce = function () {
$scope.myself.age--;
};
})
.component('echo', {
controllerAs: 'vm',
bindings: {
myName: "<",
myAge: '<'
},
template: '<div>我的名字是{{vm.name}},我今年{{vm.age}}了</div>',
controller: function () {
this.$onInit = function () {
this.name = this.myName;
this.age = this.myAge;
};
}
});

我将父作用域对象中一条属性通过bindings传递给子组件后,通过点击事件不断减小age属性,可以看到组件中没办法感知到变化。那么这种情况就可以利用$onChanges实现,我们修改代码:

angular.module('myApp', [])
.controller('myCtrl', function ($scope) {
$scope.myself = {
name: '听风是风',
age: 26
};
$scope.reduce = function () {
$scope.myself.age--;
};
})
.component('echo', {
controllerAs: 'vm',
bindings: {
myName: "<",
myAge: '<'
},
template: '<div>我的名字是{{vm.name}},我今年{{vm.age}}了</div>',
controller: function () {
this.$onInit = function () {
this.name = this.myName;
};
this.$onChanges = function (changes) {
console.log(changes);
this.age = changes.myAge.currentValue;
};
}
});

有人肯定会问了,这个$onChanges这么好用,那能不能直接取代$onInit做初始化呢,这个changes参数又是啥,我们可以打印它:

changes代表的正是bindings中变化的变量,这里一共输出了2次,第一次是组件初始化时myName与myAge从无到有,第二次输出是因为点击导致age减少,由此可见$onChanges只能监听到bindings中变化的变量,并不适合做初始化。

 肆 ❀ $postLink

我们知道directive因为编译函数与链接函数的存在,我们可以在DOM编译阶段操作DOM以及链接阶段绑定数据,而component提供的$postLink方法可在组件自身模板和其子级组件模板已链接之后被调用。

通过angularjs生命周期我们知道,组件总是先编译完成,再将模板与scope链接,且链接过程是从子往父回溯绑定,由这一点可以确定$postLink执行时子组件的模板一定已经编译完成,我们来看个例子:

<div ng-controller="myCtrl">
<jack>
<echo></echo>
</jack>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope) {})
.component('jack', {
transclude: true,
template: '<div>parent</div><div ng-transclude></div>',
controller: function ($scope) {
var child = document.querySelector('#child');
console.dir(child);//?
this.$postLink = function () {
var child = document.querySelector('#child');
console.dir(child);//?
}; }
})
.component('echo', {
template: '<div id="child">child</div>',
});

在上述代码中,我想在父组件jack的控制器中获取子组件id为child的元素,于是我分别在控制器外层与$postLink中分别获取两次,并打印它们,可以看到$postLink中成功获取到了子模板元素。

我们根据断点查看DOM变化,在打印第一个时,子组件模板还没加载,而跑到$postLink时,子组件模板已经加载完毕了,所以此时我们可以获取到正确的DOM。

 伍 ❀ $onDestroy

$onDestroy用于在作用域被销毁时,用于清除掉那些我们先前自定义的事件监听或者定时器等。

我们知道angularjs在$scope上提供了一个$destory方法用于主动销毁当前作用域,对于这个方法陌生可以看看angularjs权威指南的解释:

我们知道就算是JS用于垃圾回收机制在操作DOM或者定时器时也得在必要的时候手动释放它们,angularjs虽然也存在自动清理作用域的情况,但它也没办法销毁我们定义的原生JS逻辑,来看个例子:

<div ng-controller="myCtrl">
<button ng-click="sayName()">sayName</button>
<button ng-click="ruin()">destory</button>
</div>
angular.module('myApp', [])
.controller('myCtrl', function ($scope, $interval) {
$scope.ruin = function () {
console.log('销毁作用域');
//销毁作用域
$scope.$destroy();
};
$scope.sayName = function () {
console.log('听风是风');
}; //原生定时器
var s1 = setInterval(function () {
console.log('我是原生定时器');
}, 3000);
//angular提供的定时器
var s2 = $interval(function () {
console.log('我是angular定时器');
}, 3000);
})

在这个例子中,我们分别用创建了一个原生定时器与angular的封装定时器,在销毁作用域后可以看到绑定的sayName失效,但两个定时器仍然在起作用。怎么感知销毁并清除掉2个定时器呢,angular是这么做的,添加如下代码:

$scope.$on('$destroy', function () {
clearInterval(s1);
$interval.cancel(s2);
})

可以看到我们通过$on监听销毁后清除了定时器起到了作用,之后定时器并未执行。

那么说道这你肯定就疑问了,$on都能监听销毁,我在组件间也能这么用,那还要什么$onDestroy钩子函数,其实我一开始也有这个疑问,我在谷歌百度了相关资料,唯一得的合理就是,脱离$scope的约束。

我们知道在angularjs早期版本数据都是推崇绑定scope上,在后面版本我们可以通过as vm的做法将数据绑定在控制器this上,而组件的出现,你会发现组件传值都是默认绑定在控制器this上,也就是说在组件component开发中我们不用注入$scope都能做到任意数据绑定,而钩子函数$onDestroy的出现真是满足了这一点。

实际开发中我们很少使用$scope.$destroy()手动销毁作用域,我们要做的仅仅是感知销毁并做要做的事情而已,来看个例子:

<body ng-controller="myCtrl">
<jack>
<echo></echo>
</jack>
</body>
angular.module('myApp', [])
.controller('myCtrl', function ($scope, $interval) {})
.component('jack', {
transclude: true,
template: '<div>我是parent</div><button ng-click="$ctrl.destroy()">destory</button><div ng-transclude></div>',
controller: function ($scope) {
var s1 = setInterval(function () {
console.log(1);
}, 3000);
//销毁作用域
this.destroy = function () {
$scope.$destroy();
};
//用了scope的销毁监听
$scope.$on('$destroy', function () {
clearInterval(s1);
});
}
})
.component('echo', {
template: '<div>我是child</div>',
controller: function () {
this.s2 = setInterval(function () {
console.log(1);
}, 3000);
//用了钩子函数的监听
this.$onDestroy = function () {
clearInterval(this.s2);
};
}
});

这里我创建了父组件jack与子组件echo,并分别用scope与钩子函数的作用域销毁监听方法,可以看到一旦父组件作用域销毁,父子组件中的监听函数都起到了作用。

但按照component拥有隔离作用域的特点,销毁父组件作用域应该不会影响子组件才对,所以这里的效果反而解释不通;我在上一个例子中销毁了外层控制器的作用域确实没对组件造成影响。

 陆 ❀ 总

其实文章说了这么多,到头来发现只有$onInit与$onChange在实际开发中会很有用,另外两个方法非常冷门,以至于在查阅资料时非常吃力,但站在了解的角度也是不错的,万一以后有需求需要使用呢?

如果对于angularjs指令,组件以及它们区别有兴趣,可以阅读博主相关文章:

angularjs 一篇文章看懂自定义指令directive

一篇文章看懂angularjs component组件

angularjs中directive指令与component组件有什么区别?

angularjs $scope与this的区别,controller as vm有何含义?

那么本文到这里结束。

 参考

翻译:深入理解Angular 1.5 中的生命周期钩子

AngularJS: $onDestroy component hook makes $scope unnecessary in hybrid Cordova mobile application events unbinding and in interval/timeout cleaning

$postLink of an angular component/directive running too early

了解angularjs中的生命周期钩子函数$onInit,$onChange,$onDestory,$postLink的更多相关文章

  1. vue 生命周期钩子函数

    实例中的生命周期钩子可以分为以下8种情况: beforeCreate: 实例刚被创建,vue所有属性都还不存在 created: 实例创建完成,但$el还不存在 beforeMount:挂载之前 mo ...

  2. Vue 实例中的生命周期钩子

    Vue 框架的入口就是 Vue 实例,其实就是框架中的 view model ,它包含页面中的业务处理逻辑.数据模型等,它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻 ...

  3. vue之生命周期钩子函数之运用

    一.什么是生命周期钩子函数: 每个 Vue 实例在被创建时都要经过一系列的初始化过程——例如,需要设置数据监听.编译模板.将实例挂载到 DOM 并在数据变化时更新 DOM 等.同时在这个过程中也会运行 ...

  4. vue生命周期 钩子函数

    首先,1.x和2.x的生命周期钩子对比: 钩子函数的树状图,红色的是我们可以利用的函数,绿色的是函数解析,蓝色的是函数执行时机 <!DOCTYPE html> <html> & ...

  5. 详解Vue 实例中的生命周期钩子

    Vue 框架的入口就是 Vue 实例,其实就是框架中的 view model ,它包含页面中的业务处理逻辑.数据模型等,它的生命周期中有多个事件钩子,让我们在控制整个Vue实例的过程时更容易形成好的逻 ...

  6. VueRouter和Vue生命周期(钩子函数)

    一.vue-router路由 1.介绍 vue-router是Vue的路由系统,用于定位资源的,在页面不刷新的情况下切换页面内容.类似于a标签,实际上在页面上展示出来的也是a标签,是锚点.router ...

  7. vue的生命周期钩子函数

    一.vue生命周期图示 二.钩子函数执行时间 beforeCreate      在创建实例之前,data只声明但没有赋值  在实例初始化之后,数据观测 (data observer) 和 event ...

  8. vue-cli脚手架 ,过滤器,生命周期钩子函数

    一.安装vue-cli脚手架 1.淘宝镜像下载 用淘宝的国内服务器来向国外的服务器请求,我们向淘宝请求,而不是由我们直接向国外的服务器请求,会大大提升请求速度,使用时,将所有的npm命令换成cnpm即 ...

  9. react生命周期钩子函数

    render在更新阶段和挂在阶段都会执行 class App extends Component { render() { return ( <div> <h1>reacet生 ...

随机推荐

  1. python为前端提供API

    作为一名前端来学习后端语言,有难度啊.这里把第一次尝试的过程做个记录 1.网上看到Python给前端提供API可以使用python的flaskweb框架 #py文件 import json from ...

  2. python之with语句结合上下文管理器

    所谓上下文管理器即在一个类中重写了__enter__方法和__exit__方法的类就可以成为上下文管理器类. 我们可以通过with语句结合上下文管理器简化一些操作. 使用with语句结合自定义上下文管 ...

  3. org.eclipse.emf.ecore.xmi.FeatureNotFoundException: Feature 'taglib' not found

    tomcat7,部署tomcat6下的项目统,报tomcat 7: IllegalArgumentException: taglib definitionnotconsistentwithspecif ...

  4. 牛客练习赛31A 地、颜色、魔法(搜索+二维数组一维表示)

    红色来源于山脉,象征着狂躁.愤怒.混乱,血雨腥风,电光火石. 蓝色来源于海岛,象征着控制.幻觉.诡计,运筹帷幄,谋定后动. 绿色来源于树林,象征着生命.蛮力.成长,横冲直撞,生生不息. 黑色来源于沼泽 ...

  5. java基础-类成员访问权限控制

    一 前言 这篇文章是很基础的一文,没多大深度,对于开发人员必然是熟练于心.本篇文章的主题是为什么java要设置类成员访问级别?其原因也很简单,就是为了面向对象的封装特性:将类成员使用不同的访问级别控制 ...

  6. golang中copy文件时,buffer设多大值合适,性能对比

    在go语言中,copy文件时,大文件使用buffer缓冲,可以明显加快时间, 但这个值多大合适呢? 除了考虑计算机的硬件资源,还要考虑CP文件的大小. 如果都是100m之内的小文件,一次CP完就可以. ...

  7. Homebrew的安装

    Homebrew是一款Mac OS平台下的软件包管理工具. 安装方法:命令行输入 /usr/bin/ruby -e "$(curl -fsSL https://raw.githubuserc ...

  8. iOS开发-APP图标、启动页、名字的设置

    APP图标.启动页.名字的设置:(较全面,但是APP启动页讲述的有漏洞) 参考链接:https://www.jianshu.com/p/2c7e181276ff APP启动页:(弥补上一文的漏洞) 参 ...

  9. MyBatis结果集一对多映射

    MyBatis结果集一对多映射 需求:重画二维码配置类,根据sizeCode将查询出来的imageCode分组. DROP TABLE IF EXISTS `size_code`; CREATE TA ...

  10. SpringMVC 自定义参数解析器.

    一.简述 有没有想过像 @RequestParam.@RequestBody 这些注解的工作原理呢?为什么 form 表单.application/json 的参数能够直接封装进 Bean 对象中呢? ...