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
@@ -1207,6 +1221,36 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1207
1221
return debugInfoEnabled ;
1208
1222
} ;
1209
1223
1224
+
1225
+ var TTL = 10 ;
1226
+ /**
1227
+ * @ngdoc method
1228
+ * @name $compileProvider#onChangesTtl
1229
+ * @description
1230
+ *
1231
+ * Sets the number of times `$onChanges` hooks can trigger new changes before giving up and
1232
+ * assuming that the model is unstable.
1233
+ *
1234
+ * The current default is 10 iterations.
1235
+ *
1236
+ * In complex applications it's possible that dependencies between `$onChanges` hooks and bindings will result
1237
+ * in several iterations of calls to these hooks. However if an application needs more than the default 10
1238
+ * iterations to stabilize then you should investigate what is causing the model to continuously change during
1239
+ * the `$onChanges` hook execution.
1240
+ *
1241
+ * Increasing the TTL could have performance implications, so you should not change it without proper justification.
1242
+ *
1243
+ * @param {number } limit The number of `$onChanges` hook iterations.
1244
+ * @returns {number|object } the current limit (or `this` if called as a setter for chaining)
1245
+ */
1246
+ this . onChangesTtl = function ( value ) {
1247
+ if ( arguments . length ) {
1248
+ TTL = value ;
1249
+ return this ;
1250
+ }
1251
+ return TTL ;
1252
+ } ;
1253
+
1210
1254
this . $get = [
1211
1255
'$injector' , '$interpolate' , '$exceptionHandler' , '$templateRequest' , '$parse' ,
1212
1256
'$controller' , '$rootScope' , '$sce' , '$animate' , '$$sanitizeUri' ,
@@ -1215,6 +1259,36 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
1215
1259
1216
1260
var SIMPLE_ATTR_NAME = / ^ \w / ;
1217
1261
var specialAttrHolder = document . createElement ( 'div' ) ;
1262
+
1263
+
1264
+
1265
+ var onChangesTtl = TTL ;
1266
+ // The onChanges hooks should all be run together in a single digest
1267
+ // When changes occur, the call to trigger their hooks will be added to this queue
1268
+ var onChangesQueue ;
1269
+
1270
+ // This function is called in a $$postDigest to trigger all the onChanges hooks in a single digest
1271
+ function flushOnChangesQueue ( ) {
1272
+ try {
1273
+ if ( ! ( -- onChangesTtl ) ) {
1274
+ // We have hit the TTL limit so reset everything
1275
+ onChangesQueue = undefined ;
1276
+ throw $compileMinErr ( 'infchng' , '{0} $onChanges() iterations reached. Aborting!\n' , TTL ) ;
1277
+ }
1278
+ // We must run this hook in an apply since the $$postDigest runs outside apply
1279
+ $rootScope . $apply ( function ( ) {
1280
+ for ( var i = 0 , ii = onChangesQueue . length ; i < ii ; ++ i ) {
1281
+ onChangesQueue [ i ] ( ) ;
1282
+ }
1283
+ // Reset the queue to trigger a new schedule next time there is a change
1284
+ onChangesQueue = undefined ;
1285
+ } ) ;
1286
+ } finally {
1287
+ onChangesTtl ++ ;
1288
+ }
1289
+ }
1290
+
1291
+
1218
1292
function Attributes ( element , attributesToCopy ) {
1219
1293
if ( attributesToCopy ) {
1220
1294
var keys = Object . keys ( attributesToCopy ) ;
@@ -2373,10 +2447,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2373
2447
}
2374
2448
} ) ;
2375
2449
2376
- // Trigger the `$onInit` method on all controllers that have one
2450
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
2377
2451
forEach ( elementControllers , function ( controller ) {
2378
- if ( isFunction ( controller . instance . $onInit ) ) {
2379
- controller . instance . $onInit ( ) ;
2452
+ var controllerInstance = controller . instance ;
2453
+ if ( isFunction ( controllerInstance . $onInit ) ) {
2454
+ controllerInstance . $onInit ( ) ;
2455
+ }
2456
+ if ( isFunction ( controllerInstance . $onDestroy ) ) {
2457
+ controllerScope . $on ( '$destroy' , function callOnDestroyHook ( ) {
2458
+ controllerInstance . $onDestroy ( ) ;
2459
+ } ) ;
2380
2460
}
2381
2461
} ) ;
2382
2462
@@ -2413,6 +2493,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2413
2493
) ;
2414
2494
}
2415
2495
2496
+ // Trigger $postLink lifecycle hooks
2497
+ forEach ( elementControllers , function ( controller ) {
2498
+ var controllerInstance = controller . instance ;
2499
+ if ( isFunction ( controllerInstance . $postLink ) ) {
2500
+ controllerInstance . $postLink ( ) ;
2501
+ }
2502
+ } ) ;
2503
+
2416
2504
// This is the function that is injected as `$transclude`.
2417
2505
// Note: all arguments are optional!
2418
2506
function controllersBoundTransclude ( scope , cloneAttachFn , futureParentElement , slotName ) {
@@ -3008,6 +3096,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3008
3096
// only occurs for isolate scopes and new scopes with controllerAs.
3009
3097
function initializeDirectiveBindings ( scope , attrs , destination , bindings , directive ) {
3010
3098
var removeWatchCollection = [ ] ;
3099
+ var changes ;
3011
3100
forEach ( bindings , function initializeBinding ( definition , scopeName ) {
3012
3101
var attrName = definition . attrName ,
3013
3102
optional = definition . optional ,
@@ -3023,6 +3112,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3023
3112
}
3024
3113
attrs . $observe ( attrName , function ( value ) {
3025
3114
if ( isString ( value ) ) {
3115
+ var oldValue = destination [ scopeName ] ;
3116
+ recordChanges ( scopeName , value , oldValue ) ;
3026
3117
destination [ scopeName ] = value ;
3027
3118
}
3028
3119
} ) ;
@@ -3094,6 +3185,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3094
3185
destination [ scopeName ] = parentGet ( scope ) ;
3095
3186
3096
3187
removeWatch = scope . $watch ( parentGet , function parentValueWatchAction ( newParentValue ) {
3188
+ var oldValue = destination [ scopeName ] ;
3189
+ recordChanges ( scopeName , newParentValue , oldValue ) ;
3097
3190
destination [ scopeName ] = newParentValue ;
3098
3191
} , parentGet . literal ) ;
3099
3192
@@ -3114,6 +3207,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3114
3207
}
3115
3208
} ) ;
3116
3209
3210
+ function recordChanges ( key , currentValue , previousValue ) {
3211
+ if ( isFunction ( destination . $onChanges ) && currentValue !== previousValue ) {
3212
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
3213
+ if ( ! onChangesQueue ) {
3214
+ scope . $$postDigest ( flushOnChangesQueue ) ;
3215
+ onChangesQueue = [ ] ;
3216
+ }
3217
+ // If we have not already queued a trigger of onChanges for this controller then do so now
3218
+ if ( ! changes ) {
3219
+ changes = { } ;
3220
+ onChangesQueue . push ( triggerOnChangesHook ) ;
3221
+ }
3222
+ // If the has been a change on this property already then we need to reuse the previous value
3223
+ if ( changes [ key ] ) {
3224
+ previousValue = changes [ key ] . previousValue ;
3225
+ }
3226
+ // Store this change
3227
+ changes [ key ] = { previousValue : previousValue , currentValue : currentValue } ;
3228
+ }
3229
+ }
3230
+
3231
+ function triggerOnChangesHook ( ) {
3232
+ destination . $onChanges ( changes ) ;
3233
+ // Now clear the changes so that we schedule onChanges when more changes arrive
3234
+ changes = undefined ;
3235
+ }
3236
+
3117
3237
return removeWatchCollection . length && function removeWatches ( ) {
3118
3238
for ( var i = 0 , ii = removeWatchCollection . length ; i < ii ; ++ i ) {
3119
3239
removeWatchCollection [ i ] ( ) ;
0 commit comments