From f825e738b8d3913c8b37a7b328acb909c987ae6e Mon Sep 17 00:00:00 2001 From: Anton Marinenko Date: Wed, 13 May 2026 15:33:06 +0300 Subject: [PATCH] fix(compiler): report error for property/event bindings on ng-content Previously, adding property bindings like [foo]="bar" or event bindings like (click)="handler()" to was silently ignored. These bindings have no effect since ng-content is not a real DOM element. This commit adds a compile-time error to warn the developer. Fixes #24259 --- .../src/render3/r3_template_transform.ts | 25 ++++++++++++++++ .../render3/r3_template_transform_spec.ts | 30 +++++++++++++++++++ 2 files changed, 55 insertions(+) diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index bf268b1cb31a..3c62bfc99b6c 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -190,7 +190,11 @@ class HtmlAstToIvyAst implements html.Visitor { let parsedElement: t.Content | t.Template | t.Element | undefined; if (preparsedElement.type === PreparsedElementType.NG_CONTENT) { const selector = preparsedElement.selectAttr; + + this.reportUnsupportedNgContentBindings(parsedProperties, boundEvents); + const attrs: t.TextAttribute[] = element.attrs.map((attr) => this.visitAttribute(attr)); + parsedElement = new t.Content( selector, attrs, @@ -298,6 +302,27 @@ class HtmlAstToIvyAst implements html.Visitor { : this._visitTextWithInterpolation(text.value, text.sourceSpan, text.tokens, text.i18n); } + private reportUnsupportedNgContentBindings( + properties: ParsedProperty[], + events: t.BoundEvent[], + ): void { + const reported = new Set(); + + for (const binding of [...properties, ...events]) { + const sourceSpan = binding.sourceSpan; + const key = `${sourceSpan.start.offset}:${sourceSpan.end.offset}`; + if (reported.has(key)) { + continue; + } + reported.add(key); + + this.reportError( + `Property and event bindings are not supported on . Binding "${sourceSpan.toString()}" will be ignored.`, + sourceSpan, + ); + } + } + visitExpansion(expansion: html.Expansion): t.Icu | null { if (!expansion.i18n) { // do not generate Icu in case it was created diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts index 4f9694630598..9463df5b5cee 100644 --- a/packages/compiler/test/render3/r3_template_transform_spec.ts +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -305,6 +305,36 @@ describe('R3 template transform', () => { ]); }); + it('should report an error for property binding on ngContent', () => { + const res = parse('', {ignoreError: true}); + expect(res.errors.length).toBe(1); + expect(res.errors[0].msg).toContain( + 'Property and event bindings are not supported on ', + ); + expect(res.errors[0].msg).toContain('[foo]'); + expectFromR3Nodes(res.nodes).toEqual([['Content', '*']]); + }); + + it('should report an error for event binding on ngContent', () => { + const res = parse('', {ignoreError: true}); + expect(res.errors.length).toBe(1); + expect(res.errors[0].msg).toContain( + 'Property and event bindings are not supported on ', + ); + expect(res.errors[0].msg).toContain('(click)'); + }); + + it('should report an error for each binding on ngContent', () => { + const res = parse('', {ignoreError: true}); + expect(res.errors.length).toBe(2); + }); + + it('should report a single error for two-way binding on ngContent', () => { + const res = parse('', {ignoreError: true}); + expect(res.errors.length).toBe(1); + expect(res.errors[0].msg).toContain('[(foo)]'); + }); + it('should indicate whether an element is void', () => { const nodes = parse('
').nodes as t.Element[]; expect(nodes[0].name).toBe('input');