fix(ivy): ExpressionChangedAfterItHasBeenCheckedError for SafeValue (#33749)
Fix #33448 PR Close #33749
This commit is contained in:
parent
cbb1d9f2bb
commit
f2828a08bd
|
@ -13,14 +13,14 @@ import {AttributeMarker, TAttributes, TNode, TNodeFlags, TNodeType} from '../int
|
|||
import {RElement} from '../interfaces/renderer';
|
||||
import {StylingMapArray, StylingMapArrayIndex, TStylingContext} from '../interfaces/styling';
|
||||
import {isDirectiveHost} from '../interfaces/type_checks';
|
||||
import {LView, RENDERER, TVIEW, TView} from '../interfaces/view';
|
||||
import {LView, RENDERER, TVIEW} from '../interfaces/view';
|
||||
import {getActiveDirectiveId, getCheckNoChangesMode, getCurrentStyleSanitizer, getLView, getSelectedIndex, incrementBindingIndex, nextBindingIndex, resetCurrentStyleSanitizer, setCurrentStyleSanitizer, setElementExitFn} from '../state';
|
||||
import {applyStylingMapDirectly, applyStylingValueDirectly, flushStyling, setClass, setStyle, updateClassViaContext, updateStyleViaContext} from '../styling/bindings';
|
||||
import {activateStylingMapFeature} from '../styling/map_based_bindings';
|
||||
import {attachStylingDebugObject} from '../styling/styling_debug';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
import {renderStringify} from '../util/misc_utils';
|
||||
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, getValue, hasClassInput, hasStyleInput, hasValueChanged, isHostStylingActive, isStylingContext, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, selectClassBasedInputName, setValue, stylingMapToString} from '../util/styling_utils';
|
||||
import {addItemToStylingMap, allocStylingMapArray, allocTStylingContext, allowDirectStyling, concatString, forceClassesAsString, forceStylesAsString, getInitialStylingValue, getStylingMapArray, getValue, hasClassInput, hasStyleInput, hasValueChanged, hasValueChangedUnwrapSafeValue, isHostStylingActive, isStylingContext, isStylingValueDefined, normalizeIntoStylingMap, patchConfig, selectClassBasedInputName, setValue, stylingMapToString} from '../util/styling_utils';
|
||||
import {getNativeByTNode, getTNode} from '../util/view_utils';
|
||||
|
||||
|
||||
|
@ -187,7 +187,7 @@ function stylingProp(
|
|||
// as well.
|
||||
if (ngDevMode && getCheckNoChangesMode()) {
|
||||
const oldValue = getValue(lView, bindingIndex);
|
||||
if (hasValueChanged(oldValue, value)) {
|
||||
if (hasValueChangedUnwrapSafeValue(oldValue, value)) {
|
||||
throwErrorIfNoChangesMode(false, oldValue, value);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@
|
|||
* Use of this source code is governed by an MIT-style license that can be
|
||||
* found in the LICENSE file at https://angular.io/license
|
||||
*/
|
||||
import {unwrapSafeValue} from '../../sanitization/bypass';
|
||||
import {PropertyAliases, TNodeFlags} from '../interfaces/node';
|
||||
import {LStylingData, StylingMapArray, StylingMapArrayIndex, TStylingContext, TStylingContextIndex, TStylingContextPropConfigFlags, TStylingNode} from '../interfaces/styling';
|
||||
import {NO_CHANGE} from '../tokens';
|
||||
|
@ -170,6 +171,14 @@ export function getPropValuesStartPosition(
|
|||
return startPosition;
|
||||
}
|
||||
|
||||
export function hasValueChangedUnwrapSafeValue(
|
||||
a: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined | {},
|
||||
b: NO_CHANGE | StylingMapArray | number | String | string | null | boolean | undefined |
|
||||
{}): boolean {
|
||||
return hasValueChanged(unwrapSafeValue(a), unwrapSafeValue(b));
|
||||
}
|
||||
|
||||
|
||||
export function hasValueChanged(
|
||||
a: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {},
|
||||
b: NO_CHANGE | StylingMapArray | number | string | null | boolean | undefined | {}): boolean {
|
||||
|
|
|
@ -87,9 +87,11 @@ class SafeResourceUrlImpl extends SafeValueImpl implements SafeResourceUrl {
|
|||
getTypeName() { return BypassType.ResourceUrl; }
|
||||
}
|
||||
|
||||
export function unwrapSafeValue(value: string | SafeValue): string {
|
||||
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity :
|
||||
value as string;
|
||||
export function unwrapSafeValue(value: SafeValue): string;
|
||||
export function unwrapSafeValue<T>(value: T): T;
|
||||
export function unwrapSafeValue<T>(value: T | SafeValue): T {
|
||||
return value instanceof SafeValueImpl ? value.changingThisBreaksApplicationSecurity as any as T :
|
||||
value as any as T;
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -2365,6 +2365,25 @@ describe('styling', () => {
|
|||
|
||||
function splitSortJoin(s: string) { return s.split(/\s+/).sort().join(' ').trim(); }
|
||||
});
|
||||
|
||||
describe('ExpressionChangedAfterItHasBeenCheckedError', () => {
|
||||
it('should not throw when bound to SafeValue', () => {
|
||||
@Component({template: `<div [style.background-image]="iconSafe"></div>`})
|
||||
class MyComp {
|
||||
icon = 'https://i.imgur.com/4AiXzf8.jpg';
|
||||
get iconSafe() { return this.sanitizer.bypassSecurityTrustStyle(`url("${this.icon}")`); }
|
||||
|
||||
constructor(private sanitizer: DomSanitizer) {}
|
||||
}
|
||||
|
||||
const fixture =
|
||||
TestBed.configureTestingModule({declarations: [MyComp]}).createComponent(MyComp);
|
||||
fixture.detectChanges(true /* Verify that check no changes does not cause an exception */);
|
||||
const div: HTMLElement = fixture.nativeElement.querySelector('div');
|
||||
expect(div.style.getPropertyValue('background-image'))
|
||||
.toEqual('url("https://i.imgur.com/4AiXzf8.jpg")');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function assertStyleCounters(countForSet: number, countForRemove: number) {
|
||||
|
|
Loading…
Reference in New Issue