feat($compile): add isFirstChange() method to onChanges object
Closes #14318 Closes #14323
This commit is contained in:
@@ -156,7 +156,7 @@ of the component. The following hook methods can be implemented:
|
||||
this element). This is a good place to put initialization code for your controller.
|
||||
* `$onChanges(changesObj)` - Called whenever one-way bindings are updated. The `changesObj` is a hash whose keys
|
||||
are the names of the bound properties that have changed, and the values are an object of the form
|
||||
`{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component such as
|
||||
`{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a component such as
|
||||
cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
|
||||
external resources, watches and event handlers.
|
||||
|
||||
+15
-3
@@ -298,8 +298,8 @@
|
||||
* this element). This is a good place to put initialization code for your controller.
|
||||
* * `$onChanges(changesObj)` - Called whenever one-way (`<`) or interpolation (`@`) bindings are updated. The
|
||||
* `changesObj` is a hash whose keys are the names of the bound properties that have changed, and the values are an
|
||||
* object of the form `{ currentValue: ..., previousValue: ... }`. Use this hook to trigger updates within a component
|
||||
* such as cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* object of the form `{ currentValue, previousValue, isFirstChange() }`. Use this hook to trigger updates within a
|
||||
* component such as cloning the bound value to prevent accidental mutation of the outer value.
|
||||
* * `$onDestroy()` - Called on a controller when its containing scope is destroyed. Use this hook for releasing
|
||||
* external resources, watches and event handlers. Note that components have their `$onDestroy()` hooks called in
|
||||
* the same order as the `$scope.$broadcast` events are triggered, which is top down. This means that parent
|
||||
@@ -846,6 +846,9 @@
|
||||
|
||||
var $compileMinErr = minErr('$compile');
|
||||
|
||||
function UNINITIALIZED_VALUE() {}
|
||||
var _UNINITIALIZED_VALUE = new UNINITIALIZED_VALUE();
|
||||
|
||||
/**
|
||||
* @ngdoc provider
|
||||
* @name $compileProvider
|
||||
@@ -3115,6 +3118,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
// the value to boolean rather than a string, so we special case this situation
|
||||
destination[scopeName] = lastValue;
|
||||
}
|
||||
recordChanges(scopeName, destination[scopeName], _UNINITIALIZED_VALUE);
|
||||
break;
|
||||
|
||||
case '=':
|
||||
@@ -3170,6 +3174,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
parentGet = $parse(attrs[attrName]);
|
||||
|
||||
destination[scopeName] = parentGet(scope);
|
||||
recordChanges(scopeName, destination[scopeName], _UNINITIALIZED_VALUE);
|
||||
|
||||
removeWatch = scope.$watch(parentGet, function parentValueWatchAction(newParentValue) {
|
||||
var oldValue = destination[scopeName];
|
||||
@@ -3211,7 +3216,7 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
previousValue = changes[key].previousValue;
|
||||
}
|
||||
// Store this change
|
||||
changes[key] = {previousValue: previousValue, currentValue: currentValue};
|
||||
changes[key] = new SimpleChange(previousValue, currentValue);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3230,6 +3235,13 @@ function $CompileProvider($provide, $$sanitizeUriProvider) {
|
||||
}];
|
||||
}
|
||||
|
||||
function SimpleChange(previous, current) {
|
||||
this.previousValue = previous;
|
||||
this.currentValue = current;
|
||||
}
|
||||
SimpleChange.prototype.isFirstChange = function() { return this.previousValue === _UNINITIALIZED_VALUE; };
|
||||
|
||||
|
||||
var PREFIX_REGEXP = /^((?:x|data)[\:\-_])/i;
|
||||
/**
|
||||
* Converts all accepted directives format into proper directive name.
|
||||
|
||||
+52
-11
@@ -3676,8 +3676,9 @@ describe('$compile', function() {
|
||||
// Now we should have a single changes entry in the log
|
||||
expect(log).toEqual([
|
||||
{
|
||||
prop1: {previousValue: undefined, currentValue: 42},
|
||||
prop2: {previousValue: undefined, currentValue: 84}
|
||||
prop1: jasmine.objectContaining({currentValue: 42}),
|
||||
prop2: jasmine.objectContaining({currentValue: 84}),
|
||||
attr: jasmine.objectContaining({currentValue: ''})
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -3689,8 +3690,8 @@ describe('$compile', function() {
|
||||
// Now we should have a single changes entry in the log
|
||||
expect(log).toEqual([
|
||||
{
|
||||
prop1: {previousValue: 42, currentValue: 17},
|
||||
prop2: {previousValue: 84, currentValue: 34}
|
||||
prop1: jasmine.objectContaining({previousValue: 42, currentValue: 17}),
|
||||
prop2: jasmine.objectContaining({previousValue: 84, currentValue: 34})
|
||||
}
|
||||
]);
|
||||
|
||||
@@ -3707,7 +3708,7 @@ describe('$compile', function() {
|
||||
// onChanges should not have been called
|
||||
expect(log).toEqual([
|
||||
{
|
||||
attr: {previousValue: '', currentValue: '22'}
|
||||
attr: jasmine.objectContaining({previousValue: '', currentValue: '22'})
|
||||
}
|
||||
]);
|
||||
});
|
||||
@@ -3739,7 +3740,7 @@ describe('$compile', function() {
|
||||
// Update val to trigger the onChanges
|
||||
$rootScope.$apply('a = 42');
|
||||
// Now the change should have the real previous value (undefined), not the intermediate one (42)
|
||||
expect(log).toEqual([{prop: {previousValue: undefined, currentValue: 126}}]);
|
||||
expect(log).toEqual([{prop: jasmine.objectContaining({currentValue: 126})}]);
|
||||
|
||||
// Clear the log
|
||||
log = [];
|
||||
@@ -3747,7 +3748,46 @@ describe('$compile', function() {
|
||||
// Update val to trigger the onChanges
|
||||
$rootScope.$apply('a = 7');
|
||||
// Now the change should have the real previous value (126), not the intermediate one, (91)
|
||||
expect(log).toEqual([{ prop: {previousValue: 126, currentValue: 21}}]);
|
||||
expect(log).toEqual([{prop: jasmine.objectContaining({previousValue: 126, currentValue: 21})}]);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
it('should trigger an initial onChanges call for each binding with the `isFirstChange()` returning true', function() {
|
||||
var log = [];
|
||||
function TestController() { }
|
||||
TestController.prototype.$onChanges = function(change) { log.push(change); };
|
||||
|
||||
angular.module('my', [])
|
||||
.component('c1', {
|
||||
controller: TestController,
|
||||
bindings: { 'prop': '<', attr: '@' }
|
||||
});
|
||||
|
||||
module('my');
|
||||
inject(function($compile, $rootScope) {
|
||||
element = $compile('<c1 prop="a" attr="{{a}}"></c1>')($rootScope);
|
||||
expect(log).toEqual([]);
|
||||
$rootScope.$apply('a = 7');
|
||||
expect(log).toEqual([
|
||||
{
|
||||
prop: jasmine.objectContaining({currentValue: 7}),
|
||||
attr: jasmine.objectContaining({currentValue: '7'})
|
||||
}
|
||||
]);
|
||||
expect(log[0].prop.isFirstChange()).toEqual(true);
|
||||
expect(log[0].attr.isFirstChange()).toEqual(true);
|
||||
|
||||
log = [];
|
||||
$rootScope.$apply('a = 9');
|
||||
expect(log).toEqual([
|
||||
{
|
||||
prop: jasmine.objectContaining({previousValue: 7, currentValue: 9}),
|
||||
attr: jasmine.objectContaining({previousValue: '7', currentValue: '9'})
|
||||
}
|
||||
]);
|
||||
expect(log[0].prop.isFirstChange()).toEqual(false);
|
||||
expect(log[0].attr.isFirstChange()).toEqual(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3786,8 +3826,8 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('val1 = 42; val2 = 17');
|
||||
|
||||
expect(log).toEqual([
|
||||
['TestController1', {prop: {previousValue: undefined, currentValue: 42}}],
|
||||
['TestController2', {prop: {previousValue: undefined, currentValue: 17}}]
|
||||
['TestController1', {prop: jasmine.objectContaining({currentValue: 42})}],
|
||||
['TestController2', {prop: jasmine.objectContaining({currentValue: 17})}]
|
||||
]);
|
||||
// A single apply should only trigger three turns of the digest loop
|
||||
expect(watchCount).toEqual(3);
|
||||
@@ -3831,8 +3871,9 @@ describe('$compile', function() {
|
||||
$rootScope.$apply('a = 42');
|
||||
|
||||
expect(log).toEqual([
|
||||
['OuterController', {prop1: {previousValue: undefined, currentValue: 42}}],
|
||||
['InnerController', {prop2: {previousValue: undefined, currentValue: 72}}]
|
||||
['OuterController', {prop1: jasmine.objectContaining({currentValue: 42})}],
|
||||
['InnerController', {prop2: jasmine.objectContaining({currentValue: undefined})}],
|
||||
['InnerController', {prop2: jasmine.objectContaining({currentValue: 72})}]
|
||||
]);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user