diff --git a/packages/compiler-cli/src/ngtsc/core/api/src/options.ts b/packages/compiler-cli/src/ngtsc/core/api/src/options.ts
index a88187f5b78..51e1b4694cc 100644
--- a/packages/compiler-cli/src/ngtsc/core/api/src/options.ts
+++ b/packages/compiler-cli/src/ngtsc/core/api/src/options.ts
@@ -112,12 +112,6 @@ export interface InternalOptions {
*/
_enableSelectorless?: boolean;
- // TODO(crisbeto): this is a temporary flag that will be removed in v20.
- /**
- * Whether to check the event side of two-way bindings.
- */
- _checkTwoWayBoundEvents?: boolean;
-
/**
* Whether this is a compilation of Angular core itself.
*
@@ -135,7 +129,8 @@ export interface InternalOptions {
* Also includes a few miscellaneous options.
*/
export interface NgCompilerOptions
- extends ts.CompilerOptions,
+ extends
+ ts.CompilerOptions,
LegacyNgcOptions,
BazelAndG3Options,
DiagnosticOptions,
diff --git a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts
index 17df0f53ac5..e4e78b3e8bb 100644
--- a/packages/compiler-cli/src/ngtsc/core/src/compiler.ts
+++ b/packages/compiler-cli/src/ngtsc/core/src/compiler.ts
@@ -1053,7 +1053,6 @@ export class NgCompiler {
const strictTemplates = this.strictTemplates;
const useInlineTypeConstructors = this.programDriver.supportsInlineOperations;
- const checkTwoWayBoundEvents = this.options['_checkTwoWayBoundEvents'] ?? false;
// Check whether the loaded version of `@angular/core` in the `ts.Program` supports unwrapping
// writable signals for type-checking. Only Angular versions greater than 17.2 have the necessary
@@ -1104,7 +1103,6 @@ export class NgCompiler {
unusedStandaloneImports:
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
allowSignalsInTwoWayBindings,
- checkTwoWayBoundEvents,
allowDomEventAssertion,
};
} else {
@@ -1137,7 +1135,6 @@ export class NgCompiler {
unusedStandaloneImports:
this.options.extendedDiagnostics?.defaultCategory || DiagnosticCategoryLabel.Warning,
allowSignalsInTwoWayBindings,
- checkTwoWayBoundEvents,
allowDomEventAssertion,
};
}
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts
index 23dab7dcf03..80d40532a22 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/model_signal_diagnostics_spec.ts
@@ -381,10 +381,7 @@ runInEachFileSystem(() => {
otherVal!: string;
`,
template: `
`,
- expected: [
- `TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
- `TestComponent.html(1, 27): Type 'boolean' is not assignable to type 'string'.`,
- ],
+ expected: [`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`],
},
{
id: 'generic inference and two-way binding to directive, mix of zone input and model',
@@ -408,10 +405,7 @@ runInEachFileSystem(() => {
genVal!: boolean;
otherVal!: string;
`,
- expected: [
- `TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`,
- `TestComponent.html(1, 10): Type 'string' is not assignable to type 'boolean'.`,
- ],
+ expected: [`TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`],
},
{
id: 'generic inference and two-way binding to directive (with `extends boolean`), all model inputs',
@@ -432,10 +426,7 @@ runInEachFileSystem(() => {
genVal!: boolean;
otherVal!: string;
`,
- expected: [
- `TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
- `TestComponent.html(1, 27): Type 'boolean' is not assignable to type 'string'.`,
- ],
+ expected: [`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`],
},
{
id: 'generic inference and two-way binding to directive (with `extends boolean`), mix of zone inputs and model',
@@ -459,10 +450,7 @@ runInEachFileSystem(() => {
genVal!: boolean;
otherVal!: string;
`,
- expected: [
- `TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`,
- `TestComponent.html(1, 27): Type 'boolean' is not assignable to type 'string'.`,
- ],
+ expected: [`TestComponent.html(1, 29): Type 'string' is not assignable to type 'boolean'.`],
},
{
id: 'generic multi-inference and two-way bindings to directive, all model inputs',
@@ -595,10 +583,7 @@ runInEachFileSystem(() => {
outputs: {valueChange: {type: 'ModelSignal
'}},
template: ``,
component: `bla = true;`,
- expected: [
- `TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`,
- `TestComponent.html(1, 10): Type 'string' is not assignable to type 'boolean'.`,
- ],
+ expected: [`TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`],
},
{
id: 'two-way binding to primitive, valid',
@@ -667,10 +652,7 @@ runInEachFileSystem(() => {
outputs: {valueChange: {type: 'ModelSignal
'}},
template: ``,
component: `val!: WritableSignal
;`,
- expected: [
- `TestComponent.html(1, 12): Type 'string' is not assignable to type 'boolean'.`,
- `TestComponent.html(1, 10): Type 'boolean' is not assignable to type 'string'.`,
- ],
+ expected: [`TestComponent.html(1, 12): Type 'string' is not assignable to type 'boolean'.`],
},
{
id: 'non-writable signal binding',
@@ -680,7 +662,6 @@ runInEachFileSystem(() => {
component: `val!: InputSignal;`,
expected: [
`TestComponent.html(1, 10): Type 'InputSignal' is not assignable to type 'boolean'.`,
- `TestComponent.html(1, 10): Type 'boolean' is not assignable to type 'InputSignal'.`,
],
},
{
@@ -701,9 +682,6 @@ runInEachFileSystem(() => {
jasmine.stringContaining(
`TestComponent.html(1, 12): Type '(v: string) => number' is not assignable to type '(v: number) => number`,
),
- jasmine.stringContaining(
- `TestComponent.html(1, 10): Type '(v: number) => number' is not assignable to type '(v: string) => number`,
- ),
],
},
];
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts
index 2a17b46dc83..b3c86afb7bf 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/output_function_diagnostics.spec.ts
@@ -31,10 +31,7 @@ runInEachFileSystem(() => {
outputs: {'valueChange': {type: 'OutputEmitterRef'}},
template: ``,
component: `bla = true;`,
- expected: [
- `TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`,
- `TestComponent.html(1, 10): Type 'string' is not assignable to type 'boolean'.`,
- ],
+ expected: [`TestComponent.html(1, 12): Type 'boolean' is not assignable to type 'string'.`],
},
{
id: 'two way data binding, valid',
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
index a62824d52b8..128f39c6ef4 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/test/type_check_block_spec.ts
@@ -875,8 +875,6 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1 = null! as i0.TwoWay;');
expect(block).toContain('_t1.input = i1.ɵunwrapWritableSignal((((this).value)));');
- expect(block).toContain('var _t2 = i1.ɵunwrapWritableSignal(((this).value));');
- expect(block).toContain('_t2 = $event;');
});
it('should handle a two-way binding to an input/output pair of a generic directive', () => {
@@ -899,8 +897,6 @@ describe('type check blocks', () => {
'var _t1 = _ctor1({ "input": (i1.ɵunwrapWritableSignal(((this).value))) });',
);
expect(block).toContain('_t1.input = i1.ɵunwrapWritableSignal((((this).value)));');
- expect(block).toContain('var _t2 = i1.ɵunwrapWritableSignal(((this).value));');
- expect(block).toContain('_t2 = $event;');
});
it('should handle a two-way binding to a model()', () => {
@@ -927,8 +923,6 @@ describe('type check blocks', () => {
expect(block).toContain(
'_t1.input[i1.ɵINPUT_SIGNAL_BRAND_WRITE_TYPE] = i1.ɵunwrapWritableSignal((((this).value)));',
);
- expect(block).toContain('var _t2 = i1.ɵunwrapWritableSignal(((this).value));');
- expect(block).toContain('_t2 = $event;');
});
it('should handle a two-way binding to an input with a transform', () => {
@@ -970,8 +964,6 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1 = null! as boolean | string;');
expect(block).toContain('_t1 = i1.ɵunwrapWritableSignal((((this).value)));');
- expect(block).toContain('var _t3 = i1.ɵunwrapWritableSignal(((this).value));');
- expect(block).toContain('_t3 = $event;');
});
describe('experimental DOM checking via lib.dom.d.ts', () => {
@@ -1099,8 +1091,6 @@ describe('type check blocks', () => {
const block = tcb(TEMPLATE, DIRECTIVES);
expect(block).toContain('var _t1 = null! as i0.TwoWay;');
expect(block).toContain('_t1.input = i1.ɵunwrapWritableSignal(((((this).value) as any)));');
- expect(block).toContain('var _t2 = i1.ɵunwrapWritableSignal((((this).value) as any));');
- expect(block).toContain('_t2 = $event;');
});
it('should detect writes to template variables', () => {
@@ -1156,7 +1146,6 @@ describe('type check blocks', () => {
controlFlowPreventingContentProjection: 'warning',
unusedStandaloneImports: 'warning',
allowSignalsInTwoWayBindings: true,
- checkTwoWayBoundEvents: true,
allowDomEventAssertion: true,
};
diff --git a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
index 3216dedc4a4..75c3e11b43e 100644
--- a/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
+++ b/packages/compiler-cli/src/ngtsc/typecheck/testing/index.ts
@@ -288,7 +288,6 @@ export const ALL_ENABLED_CONFIG: Readonly
= {
controlFlowPreventingContentProjection: 'warning',
unusedStandaloneImports: 'warning',
allowSignalsInTwoWayBindings: true,
- checkTwoWayBoundEvents: true,
allowDomEventAssertion: true,
};
@@ -443,7 +442,6 @@ export function tcb(
enableTemplateTypeChecker: false,
useInlineTypeConstructors: true,
allowSignalsInTwoWayBindings: true,
- checkTwoWayBoundEvents: true,
allowDomEventAssertion: true,
...config,
};
diff --git a/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts b/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts
index 358de9ad349..6122cfd3720 100644
--- a/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/authoring_inputs_spec.ts
@@ -21,7 +21,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
env = NgtscTestEnvironment.setup(testFiles);
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
});
it('should handle a basic, primitive valued input', () => {
@@ -255,9 +255,8 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`);
- expect(diags[1].messageText).toBe(`Type 'string' is not assignable to type 'number'.`);
});
describe('type checking', () => {
diff --git a/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts b/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts
index 1dc192d4f62..d6359255929 100644
--- a/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/authoring_models_spec.ts
@@ -21,7 +21,7 @@ runInEachFileSystem(() => {
beforeEach(() => {
env = NgtscTestEnvironment.setup(testFiles);
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
});
it('should declare an input/output pair for a field initialized to a model()', () => {
@@ -294,9 +294,8 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`);
- expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`);
});
it('should check a signal value bound to a model input via a two-way binding', () => {
@@ -323,9 +322,8 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`);
- expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`);
});
it('should check two-way binding of a signal to a decorator-based input/output pair', () => {
@@ -353,9 +351,8 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(`Type 'boolean' is not assignable to type 'number'.`);
- expect(diags[1].messageText).toBe(`Type 'number' is not assignable to type 'boolean'.`);
});
it('should not allow a non-writable signal to be assigned to a model', () => {
@@ -382,13 +379,10 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toBe(
`Type 'InputSignal' is not assignable to type 'number'.`,
);
- expect(diags[1].messageText).toBe(
- `Type 'number' is not assignable to type 'InputSignal'.`,
- );
});
it('should allow a model signal to be bound to another model signal', () => {
@@ -501,17 +495,12 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toEqual(
jasmine.objectContaining({
messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`,
}),
);
- expect(diags[1].messageText).toEqual(
- jasmine.objectContaining({
- messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`,
- }),
- );
});
it('should check generic two-way model binding with a signal value', () => {
@@ -538,17 +527,12 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toEqual(
jasmine.objectContaining({
messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`,
}),
);
- expect(diags[1].messageText).toEqual(
- jasmine.objectContaining({
- messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`,
- }),
- );
});
it('should report unwrapped signals assigned to a model in a one-way binding', () => {
diff --git a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
index 0c1d09b043d..fe4860f9c99 100644
--- a/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
+++ b/packages/compiler-cli/test/ngtsc/template_typecheck_spec.ts
@@ -543,7 +543,7 @@ runInEachFileSystem(() => {
});
it('should type check a two-way binding to a generic property', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
env.write(
'test.ts',
`
@@ -566,21 +566,16 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toEqual(
jasmine.objectContaining({
messageText: `Type '{ id: number; }' is not assignable to type '{ id: string; }'.`,
}),
);
- expect(diags[1].messageText).toEqual(
- jasmine.objectContaining({
- messageText: `Type '{ id: string; }' is not assignable to type '{ id: number; }'.`,
- }),
- );
});
it('should use the setter type when assigning using a two-way binding to an input with different getter and setter types', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
env.write(
'test.ts',
`
@@ -615,7 +610,7 @@ runInEachFileSystem(() => {
});
it('should type check a two-way binding to a function value', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
env.write(
'test.ts',
`
@@ -640,21 +635,16 @@ runInEachFileSystem(() => {
);
const diags = env.driveDiagnostics();
- expect(diags.length).toBe(2);
+ expect(diags.length).toBe(1);
expect(diags[0].messageText).toEqual(
jasmine.objectContaining({
messageText: `Type '(val: string) => number' is not assignable to type 'TestFn'.`,
}),
);
- expect(diags[1].messageText).toEqual(
- jasmine.objectContaining({
- messageText: `Type 'TestFn' is not assignable to type '(val: string) => number'.`,
- }),
- );
});
it('should be able to cast to any in a two-way binding', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
env.write(
'test.ts',
`
@@ -680,34 +670,6 @@ runInEachFileSystem(() => {
expect(diags.length).toBe(0);
});
- it('should type check a two-way binding to input/output pair where the input has a wider type than the output', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
- env.write(
- 'test.ts',
- `
- import {Component, Directive, Input, Output, EventEmitter} from '@angular/core';
-
- @Directive({selector: '[dir]'})
- export class Dir {
- @Input() value: string | number;
- @Output() valueChange = new EventEmitter();
- }
-
- @Component({
- template: '',
- imports: [Dir],
- })
- export class App {
- value = 'hello';
- }
- `,
- );
-
- const diags = env.driveDiagnostics();
- expect(diags.length).toBe(1);
- expect(diags[0].messageText).toBe(`Type 'number' is not assignable to type 'string'.`);
- });
-
it('should check the fallback content of ng-content', () => {
env.write(
'test.ts',
@@ -3084,7 +3046,7 @@ runInEachFileSystem(() => {
});
it('should type check a two-way binding to an input with a transform', () => {
- env.tsconfig({strictTemplates: true, _checkTwoWayBoundEvents: true});
+ env.tsconfig({strictTemplates: true});
env.write(
'test.ts',
`
diff --git a/packages/compiler/src/typecheck/api.ts b/packages/compiler/src/typecheck/api.ts
index c4c290631f9..ccf5adb14ed 100644
--- a/packages/compiler/src/typecheck/api.ts
+++ b/packages/compiler/src/typecheck/api.ts
@@ -288,9 +288,4 @@ export interface TypeCheckingConfig {
* Whether to descend into the bodies of control flow blocks (`@if`, `@switch` and `@for`).
*/
checkControlFlowBodies: boolean;
-
- /**
- * Whether the event side of a two-way binding should be type checked.
- */
- checkTwoWayBoundEvents: boolean;
}
diff --git a/packages/compiler/src/typecheck/ops/events.ts b/packages/compiler/src/typecheck/ops/events.ts
index 307f2bcf0a4..ffddcbbac48 100644
--- a/packages/compiler/src/typecheck/ops/events.ts
+++ b/packages/compiler/src/typecheck/ops/events.ts
@@ -297,24 +297,7 @@ function tcbCreateEventHandler(
statements.push(assertionExpression);
}
- // TODO(crisbeto): remove the `checkTwoWayBoundEvents` check in v20.
- if (event.type === ParsedEventType.TwoWay && tcb.env.config.checkTwoWayBoundEvents) {
- // If we're dealing with a two-way event, we create a variable initialized to the unwrapped
- // signal value of the expression and then we assign `$event` to it. Note that in most cases
- // this will already be covered by the corresponding input binding, however it allows us to
- // handle the case where the input has a wider type than the output (see #58971).
- const target = tcb.allocateId();
- const initializer = tcb.env.config.allowSignalsInTwoWayBindings
- ? unwrapWritableSignal(handler, tcb)
- : handler;
-
- statements.push(
- new TcbExpr(`var ${target} = ${initializer.print()}`),
- new TcbExpr(`${target} = ${EVENT_PARAMETER}`),
- );
- } else {
- statements.push(handler);
- }
+ statements.push(handler);
let eventParamType: string | undefined;
if (eventType === EventParamType.Infer) {