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 (`<`) bindings are updated. The `changesObj` is a hash
300
+ * whose keys are the names of the bound properties that have changed, and the values are an object of the form
301
+ * `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as
302
+ * 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.
305
+ * * `$postLink` - Called after this controller's element and its children have been linked. Similar to the post-link
306
+ * function this hook can be used to set up DOM event handlers and do direct DOM manipulation.
307
+ * Note that child elements that contain `templateUrl` directives will not have been compiled and linked since
308
+ * they are waiting for their template to load asynchronously and their own compilation and linking has been
309
+ * suspended until that occurs.
310
+ *
299
311
*
300
312
* #### `require`
301
313
* Require another directive and inject its controller as the fourth argument to the linking function. The
474
486
*
475
487
* * `iElement` - instance element - The element where the directive is to be used. It is safe to
476
488
* manipulate the children of the element only in `postLink` function since the children have
477
- * already been linked.
489
+ * already have been linked.
478
490
*
479
491
* * `iAttrs` - instance attributes - Normalized list of attributes declared on this element shared
480
492
* between all directive linking functions.
@@ -1215,6 +1227,24 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1215
1227
1216
1228
var SIMPLE_ATTR_NAME = / ^ \w / ;
1217
1229
var specialAttrHolder = document . createElement ( 'div' ) ;
1230
+
1231
+ // The onChanges hooks should all be run together in a single digest
1232
+ // When changes occur, the call to trigger their hooks will be added to this queue
1233
+ var onChangesQueue ;
1234
+
1235
+ // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
1236
+ function flushOnChangesQueue ( ) {
1237
+ // We must run this hook in an apply since the $$postDigest runs outside apply
1238
+ $rootScope . $apply ( function ( ) {
1239
+ for ( var i = 0 , ii = onChangesQueue . length ; i < ii ; ++ i ) {
1240
+ onChangesQueue [ i ] ( ) ;
1241
+ }
1242
+ // Reset the queue to trigger a new schedule next time there is a change
1243
+ onChangesQueue = undefined ;
1244
+ } ) ;
1245
+ }
1246
+
1247
+
1218
1248
function Attributes ( element , attributesToCopy ) {
1219
1249
if ( attributesToCopy ) {
1220
1250
var keys = Object . keys ( attributesToCopy ) ;
@@ -2360,10 +2390,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2360
2390
}
2361
2391
} ) ;
2362
2392
2363
- // Trigger the `$onInit` method on all controllers that have one
2393
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
2364
2394
forEach ( elementControllers , function ( controller ) {
2365
- if ( isFunction ( controller . instance . $onInit ) ) {
2366
- controller . instance . $onInit ( ) ;
2395
+ var controllerInstance = controller . instance ;
2396
+ if ( isFunction ( controllerInstance . $onInit ) ) {
2397
+ controllerInstance . $onInit ( ) ;
2398
+ }
2399
+ if ( isFunction ( controllerInstance . $onDestroy ) ) {
2400
+ controllerScope . $on ( '$destroy' , function callOnDestroyHook ( ) {
2401
+ controllerInstance . $onDestroy ( ) ;
2402
+ } ) ;
2367
2403
}
2368
2404
} ) ;
2369
2405
@@ -2400,6 +2436,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2400
2436
) ;
2401
2437
}
2402
2438
2439
+ // Trigger $postLink lifecycle hooks
2440
+ forEach ( elementControllers , function ( controller ) {
2441
+ var controllerInstance = controller . instance ;
2442
+ if ( isFunction ( controllerInstance . $postLink ) ) {
2443
+ controllerInstance . $postLink ( ) ;
2444
+ }
2445
+ } ) ;
2446
+
2403
2447
// This is the function that is injected as `$transclude`.
2404
2448
// Note: all arguments are optional!
2405
2449
function controllersBoundTransclude ( scope , cloneAttachFn , futureParentElement , slotName ) {
@@ -2995,6 +3039,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2995
3039
// only occurs for isolate scopes and new scopes with controllerAs.
2996
3040
function initializeDirectiveBindings ( scope , attrs , destination , bindings , directive ) {
2997
3041
var removeWatchCollection = [ ] ;
3042
+ var changes ;
2998
3043
forEach ( bindings , function initializeBinding ( definition , scopeName ) {
2999
3044
var attrName = definition . attrName ,
3000
3045
optional = definition . optional ,
@@ -3010,6 +3055,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3010
3055
}
3011
3056
attrs . $observe ( attrName , function ( value ) {
3012
3057
if ( isString ( value ) ) {
3058
+ var oldValue = destination [ scopeName ] ;
3059
+ recordChanges ( scopeName , value , oldValue ) ;
3013
3060
destination [ scopeName ] = value ;
3014
3061
}
3015
3062
} ) ;
@@ -3081,6 +3128,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3081
3128
destination [ scopeName ] = parentGet ( scope ) ;
3082
3129
3083
3130
removeWatch = scope . $watch ( parentGet , function parentValueWatchAction ( newParentValue ) {
3131
+ var oldValue = destination [ scopeName ] ;
3132
+ recordChanges ( scopeName , newParentValue , oldValue ) ;
3084
3133
destination [ scopeName ] = newParentValue ;
3085
3134
} , parentGet . literal ) ;
3086
3135
@@ -3101,6 +3150,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3101
3150
}
3102
3151
} ) ;
3103
3152
3153
+ function recordChanges ( key , currentValue , previousValue ) {
3154
+ if ( isFunction ( destination . $onChanges ) && currentValue !== previousValue ) {
3155
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
3156
+ if ( ! onChangesQueue ) {
3157
+ scope . $$postDigest ( flushOnChangesQueue ) ;
3158
+ onChangesQueue = [ ] ;
3159
+ }
3160
+ // If we have not already queued a trigger of onChanges for this controller then do so now
3161
+ if ( ! changes ) {
3162
+ changes = { } ;
3163
+ onChangesQueue . push ( triggerOnChangesHook ) ;
3164
+ }
3165
+ // If the has been a change on this property already then we need to reuse the previous value
3166
+ if ( changes [ key ] ) {
3167
+ previousValue = changes [ key ] . previousValue ;
3168
+ }
3169
+ // Store this change
3170
+ changes [ key ] = { previousValue : previousValue , currentValue : currentValue } ;
3171
+ }
3172
+ }
3173
+
3174
+ function triggerOnChangesHook ( ) {
3175
+ destination . $onChanges ( changes ) ;
3176
+ // Now clear the changes so that we schedule onChanges when more changes arrive
3177
+ changes = undefined ;
3178
+ }
3179
+
3104
3180
return removeWatchCollection . length && function removeWatches ( ) {
3105
3181
for ( var i = 0 , ii = removeWatchCollection . length ; i < ii ; ++ i ) {
3106
3182
removeWatchCollection [ i ] ( ) ;
0 commit comments