mirror of
https://github.com/angular/angular.git
synced 2026-09-14 13:54:52 +08:00
fix(core): do not allow setInput to be used with inputBinding (#60137)
Calling `setInput` while the component already has an `inputBinding` active can lead to inconsistent state. These changes add an error that will be thrown if that's the case. PR Close #60137
This commit is contained in:
committed by
Miles Malerba
parent
fe57332fc5
commit
3459faadbf
@@ -79,6 +79,8 @@ export const enum RuntimeErrorCode {
|
||||
// (undocumented)
|
||||
INVALID_MULTI_PROVIDER = -209,
|
||||
// (undocumented)
|
||||
INVALID_SET_INPUT_CALL = 317,
|
||||
// (undocumented)
|
||||
INVALID_SKIP_HYDRATION_HOST = -504,
|
||||
// (undocumented)
|
||||
LOOP_TRACK_DUPLICATE_KEYS = -955,
|
||||
|
||||
@@ -62,6 +62,7 @@ export const enum RuntimeErrorCode {
|
||||
UNINITIALIZED_LET_ACCESS = 314,
|
||||
NO_BINDING_TARGET = 315,
|
||||
INVALID_BINDING_TARGET = 316,
|
||||
INVALID_SET_INPUT_CALL = 317,
|
||||
|
||||
// Bootstrap Errors
|
||||
MULTIPLE_PLATFORMS = 400,
|
||||
|
||||
@@ -260,6 +260,9 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
|
||||
rootViewInjector,
|
||||
)
|
||||
: createHostElement(cmpDef, hostRenderer);
|
||||
const hasInputBindings =
|
||||
componentBindings?.some(isInputBinding) ||
|
||||
directives?.some((d) => typeof d !== 'function' && d.bindings.some(isInputBinding));
|
||||
|
||||
const rootLView = createLView<T>(
|
||||
null,
|
||||
@@ -355,7 +358,7 @@ export class ComponentFactory<T> extends AbstractComponentFactory<T> {
|
||||
leaveView();
|
||||
}
|
||||
|
||||
return new ComponentRef(this.componentType, rootLView);
|
||||
return new ComponentRef(this.componentType, rootLView, !!hasInputBindings);
|
||||
} finally {
|
||||
setActiveConsumer(prevConsumer);
|
||||
}
|
||||
@@ -454,6 +457,10 @@ function getRootTViewTemplate(
|
||||
};
|
||||
}
|
||||
|
||||
function isInputBinding(binding: Binding): boolean {
|
||||
return binding[BINDING].kind === 'input';
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents an instance of a Component created via a {@link ComponentFactory}.
|
||||
*
|
||||
@@ -473,7 +480,8 @@ export class ComponentRef<T> extends AbstractComponentRef<T> {
|
||||
|
||||
constructor(
|
||||
componentType: Type<T>,
|
||||
private _rootLView: LView,
|
||||
private readonly _rootLView: LView,
|
||||
private readonly _hasInputBindings: boolean,
|
||||
) {
|
||||
super();
|
||||
this._tNode = getTNode(_rootLView[TVIEW], HEADER_OFFSET) as TElementNode;
|
||||
@@ -487,6 +495,13 @@ export class ComponentRef<T> extends AbstractComponentRef<T> {
|
||||
}
|
||||
|
||||
override setInput(name: string, value: unknown): void {
|
||||
if (this._hasInputBindings && ngDevMode) {
|
||||
throw new RuntimeError(
|
||||
RuntimeErrorCode.INVALID_SET_INPUT_CALL,
|
||||
'Cannot call `setInput` on a component that is using the `inputBinding` function.',
|
||||
);
|
||||
}
|
||||
|
||||
const tNode = this._tNode;
|
||||
this.previousInputValues ??= new Map();
|
||||
// Do not set the input if it is the same as the last value
|
||||
|
||||
@@ -1084,6 +1084,28 @@ describe('createComponent', () => {
|
||||
ref.changeDetectorRef.detectChanges();
|
||||
}).toThrowError(/RootDir does not have an input with a public name of "someInput"/);
|
||||
});
|
||||
|
||||
it('should throw when using setInput on a component already using inputBindings', () => {
|
||||
@Component({template: ''})
|
||||
class RootComp {
|
||||
@Input() someInput = '';
|
||||
}
|
||||
|
||||
const hostElement = document.createElement('div');
|
||||
const environmentInjector = TestBed.inject(EnvironmentInjector);
|
||||
const ref = createComponent(RootComp, {
|
||||
hostElement,
|
||||
environmentInjector,
|
||||
bindings: [inputBinding('someInput', () => 'hello')],
|
||||
});
|
||||
ref.changeDetectorRef.detectChanges();
|
||||
|
||||
expect(() => {
|
||||
ref.setInput('someInput', 'changed');
|
||||
}).toThrowError(
|
||||
/Cannot call `setInput` on a component that is using the `inputBinding` function/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('error checking', () => {
|
||||
|
||||
Reference in New Issue
Block a user