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 ) ;
@@ -2360,10 +2434,16 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2360
2434
}
2361
2435
} ) ;
2362
2436
2363
- // Trigger the `$onInit` method on all controllers that have one
2437
+ // Handle the init and destroy lifecycle hooks on all controllers that have them
2364
2438
forEach ( elementControllers , function ( controller ) {
2365
- if ( isFunction ( controller . instance . $onInit ) ) {
2366
- controller . instance . $onInit ( ) ;
2439
+ var controllerInstance = controller . instance ;
2440
+ if ( isFunction ( controllerInstance . $onInit ) ) {
2441
+ controllerInstance . $onInit ( ) ;
2442
+ }
2443
+ if ( isFunction ( controllerInstance . $onDestroy ) ) {
2444
+ controllerScope . $on ( '$destroy' , function callOnDestroyHook ( ) {
2445
+ controllerInstance . $onDestroy ( ) ;
2446
+ } ) ;
2367
2447
}
2368
2448
} ) ;
2369
2449
@@ -2400,6 +2480,14 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2400
2480
) ;
2401
2481
}
2402
2482
2483
+ // Trigger $postLink lifecycle hooks
2484
+ forEach ( elementControllers , function ( controller ) {
2485
+ var controllerInstance = controller . instance ;
2486
+ if ( isFunction ( controllerInstance . $postLink ) ) {
2487
+ controllerInstance . $postLink ( ) ;
2488
+ }
2489
+ } ) ;
2490
+
2403
2491
// This is the function that is injected as `$transclude`.
2404
2492
// Note: all arguments are optional!
2405
2493
function controllersBoundTransclude ( scope , cloneAttachFn , futureParentElement , slotName ) {
@@ -2995,6 +3083,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
2995
3083
// only occurs for isolate scopes and new scopes with controllerAs.
2996
3084
function initializeDirectiveBindings ( scope , attrs , destination , bindings , directive ) {
2997
3085
var removeWatchCollection = [ ] ;
3086
+ var changes ;
2998
3087
forEach ( bindings , function initializeBinding ( definition , scopeName ) {
2999
3088
var attrName = definition . attrName ,
3000
3089
optional = definition . optional ,
@@ -3010,6 +3099,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3010
3099
}
3011
3100
attrs . $observe ( attrName , function ( value ) {
3012
3101
if ( isString ( value ) ) {
3102
+ var oldValue = destination [ scopeName ] ;
3103
+ recordChanges ( scopeName , value , oldValue ) ;
3013
3104
destination [ scopeName ] = value ;
3014
3105
}
3015
3106
} ) ;
@@ -3081,6 +3172,8 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3081
3172
destination [ scopeName ] = parentGet ( scope ) ;
3082
3173
3083
3174
removeWatch = scope . $watch ( parentGet , function parentValueWatchAction ( newParentValue ) {
3175
+ var oldValue = destination [ scopeName ] ;
3176
+ recordChanges ( scopeName , newParentValue , oldValue ) ;
3084
3177
destination [ scopeName ] = newParentValue ;
3085
3178
} , parentGet . literal ) ;
3086
3179
@@ -3101,6 +3194,33 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
3101
3194
}
3102
3195
} ) ;
3103
3196
3197
+ function recordChanges ( key , currentValue , previousValue ) {
3198
+ if ( isFunction ( destination . $onChanges ) && currentValue !== previousValue ) {
3199
+ // If we have not already scheduled the top level onChangesQueue handler then do so now
3200
+ if ( ! onChangesQueue ) {
3201
+ scope . $$postDigest ( flushOnChangesQueue ) ;
3202
+ onChangesQueue = [ ] ;
3203
+ }
3204
+ // If we have not already queued a trigger of onChanges for this controller then do so now
3205
+ if ( ! changes ) {
3206
+ changes = { } ;
3207
+ onChangesQueue . push ( triggerOnChangesHook ) ;
3208
+ }
3209
+ // If the has been a change on this property already then we need to reuse the previous value
3210
+ if ( changes [ key ] ) {
3211
+ previousValue = changes [ key ] . previousValue ;
3212
+ }
3213
+ // Store this change
3214
+ changes [ key ] = { previousValue : previousValue , currentValue : currentValue } ;
3215
+ }
3216
+ }
3217
+
3218
+ function triggerOnChangesHook ( ) {
3219
+ destination . $onChanges ( changes ) ;
3220
+ // Now clear the changes so that we schedule onChanges when more changes arrive
3221
+ changes = undefined ;
3222
+ }
3223
+
3104
3224
return removeWatchCollection . length && function removeWatches ( ) {
3105
3225
for ( var i = 0 , ii = removeWatchCollection . length ; i < ii ; ++ i ) {
3106
3226
removeWatchCollection [ i ] ( ) ;
0 commit comments