293
293
* `true` if the specified slot contains content (i.e. one or more DOM nodes).
294
294
*
295
295
* The controller can provide the following methods that act as life-cycle hooks:
296
- * * `$onInit` - Called on each controller after all the controllers on an element have been constructed and
296
+ * * `$onInit() ` - Called on each controller after all the controllers on an element have been constructed and
297
297
* had their bindings initialized (and before the pre & post linking functions for the directives on
298
298
* this element). This is a good place to put initialization code for your controller.
299
+ * * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
300
+ * `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
301
+ * object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component
302
+ * such as cloning the bound value to prevent accidental mutation of the outer value.
303
+ * * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
304
+ * external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
305
+ * the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
306
+ * components will have their `$onDestroy()` hook called before child components.
307
+ * * `$postLink()` - Called after this controller's element and its children have been linked. Similar to the post-link
308
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
309
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
310
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
311
+ * suspended until that occurs.
312
+ *
299
313
*
300
314
* #### `require`
301
315
* Require another directive and inject its controller as the fourth argument to the linking function. The
@@ -1215,6 +1229,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1215
1229
1216
1230
var SIMPLE_ATTR_NAME = / ^ \w / ;
1217
1231
var specialAttrHolder = document . createElement ( 'div' ) ;
1232
+
1233
+ // The onChanges hooks should all be run together in a single digest
1234
+ // When changes occur, the call to trigger their hooks will be added to this queue
1235
+ var onChangesQueue ;
1236
+
1237
+ // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
1238
+ function flushOnChangesQueue ( ) {
1239
+ // We must run this hook in an apply since the $$postDigest runs outside apply
1240
+ $rootScope . $apply ( function ( ) {
1241
+ for ( var i = 0 , ii = onChangesQueue . length ; i < ii ; ++ i ) {
1242
+ onChangesQueue [ i ] ( ) ;
1243
+ }
1244
+ // Reset the queue to trigger a new schedule next time there is a change
1245
+ onChangesQueue = undefined ;
1246
+ } ) ;
1247
+ }
1248
+
1249
+
1218
1250
function Attributes ( element , attributesToCopy ) {
1219
1251
if ( attributesToCopy ) {
1220
1252
var keys = Object . keys ( attributesToCopy ) ;
@@ -2360,10 +2392,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2360
2392
}
2361
2393
} ) ;
2362
2394
2363
- // Trigger the `$onInit` method on all controllers that have one
2395
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
2364
2396
forEach ( elementControllers , function ( controller ) {
2365
- if ( isFunction ( controller . instance . $onInit ) ) {
2366
- controller . instance . $onInit ( ) ;
2397
+ var controllerInstance = controller . instance ;
2398
+ if ( isFunction ( controllerInstance . $onInit ) ) {
2399
+ controllerInstance . $onInit ( ) ;
2400
+ }
2401
+ if ( isFunction ( controllerInstance . $onDestroy ) ) {
2402
+ controllerScope . $on ( '$destroy' , function callOnDestroyHook ( ) {
2403
+ controllerInstance . $onDestroy ( ) ;
2404
+ } ) ;
2367
2405
}
2368
2406
} ) ;
2369
2407
@@ -2400,6 +2438,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2400
2438
) ;
2401
2439
}
2402
2440
2441
+ // Trigger $postLink lifecycle hooks
2442
+ forEach ( elementControllers , function ( controller ) {
2443
+ var controllerInstance = controller . instance ;
2444
+ if ( isFunction ( controllerInstance . $postLink ) ) {
2445
+ controllerInstance . $postLink ( ) ;
2446
+ }
2447
+ } ) ;
2448
+
2403
2449
// This is the function that is injected as `$transclude`.
2404
2450
// Note: all arguments are optional!
2405
2451
function controllersBoundTransclude ( scope , cloneAttachFn , futureParentElement , slotName ) {
@@ -2995,6 +3041,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2995
3041
// only occurs for isolate scopes and new scopes with controllerAs.
2996
3042
function initializeDirectiveBindings ( scope , attrs , destination , bindings , directive ) {
2997
3043
var removeWatchCollection = [ ] ;
3044
+ var changes ;
2998
3045
forEach ( bindings , function initializeBinding ( definition , scopeName ) {
2999
3046
var attrName = definition . attrName ,
3000
3047
optional = definition . optional ,
@@ -3010,6 +3057,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3010
3057
}
3011
3058
attrs . $observe ( attrName , function ( value ) {
3012
3059
if ( isString ( value ) ) {
3060
+ var oldValue = destination [ scopeName ] ;
3061
+ recordChanges ( scopeName , value , oldValue ) ;
3013
3062
destination [ scopeName ] = value ;
3014
3063
}
3015
3064
} ) ;
@@ -3081,6 +3130,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3081
3130
destination [ scopeName ] = parentGet ( scope ) ;
3082
3131
3083
3132
removeWatch = scope . $watch ( parentGet , function parentValueWatchAction ( newParentValue ) {
3133
+ var oldValue = destination [ scopeName ] ;
3134
+ recordChanges ( scopeName , newParentValue , oldValue ) ;
3084
3135
destination [ scopeName ] = newParentValue ;
3085
3136
} , parentGet . literal ) ;
3086
3137
@@ -3101,6 +3152,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3101
3152
}
3102
3153
} ) ;
3103
3154
3155
+ function recordChanges ( key , currentValue , previousValue ) {
3156
+ if ( isFunction ( destination . $onChanges ) && currentValue !== previousValue ) {
3157
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
3158
+ if ( ! onChangesQueue ) {
3159
+ scope . $$postDigest ( flushOnChangesQueue ) ;
3160
+ onChangesQueue = [ ] ;
3161
+ }
3162
+ // If we have not already queued a trigger of onChanges for this controller then do so now
3163
+ if ( ! changes ) {
3164
+ changes = { } ;
3165
+ onChangesQueue . push ( triggerOnChangesHook ) ;
3166
+ }
3167
+ // If the has been a change on this property already then we need to reuse the previous value
3168
+ if ( changes [ key ] ) {
3169
+ previousValue = changes [ key ] . previousValue ;
3170
+ }
3171
+ // Store this change
3172
+ changes [ key ] = { previousValue : previousValue , currentValue : currentValue } ;
3173
+ }
3174
+ }
3175
+
3176
+ function triggerOnChangesHook ( ) {
3177
+ destination . $onChanges ( changes ) ;
3178
+ // Now clear the changes so that we schedule onChanges when more changes arrive
3179
+ changes = undefined ;
3180
+ }
3181
+
3104
3182
return removeWatchCollection . length && function removeWatches ( ) {
3105
3183
for ( var i = 0 , ii = removeWatchCollection . length ; i < ii ; ++ i ) {
3106
3184
removeWatchCollection [ i ] ( ) ;
0 commit comments