diff --git a/packages/compiler/src/render3/r3_template_transform.ts b/packages/compiler/src/render3/r3_template_transform.ts index 7b7e746add8..0567f0d0c71 100644 --- a/packages/compiler/src/render3/r3_template_transform.ts +++ b/packages/compiler/src/render3/r3_template_transform.ts @@ -217,9 +217,7 @@ class HtmlAstToIvyAst implements html.Visitor { attrs.bound, boundEvents, directives, - [ - /* no template attributes */ - ], + [/* no template attributes */], children, references, variables, @@ -560,6 +558,11 @@ class HtmlAstToIvyAst implements html.Visitor { predicate: (blockName: string) => boolean, ): html.Block[] { const relatedBlocks: html.Block[] = []; + // Whitespace-only text nodes to mark as processed only if a connected block follows them. + // We defer this so that significant whitespace (e.g. &ngsp; or  , both of which look + // like insignificant whitespace by the time we get here) between two unrelated @if blocks + // is not silently eaten. + const pendingTextNodes: html.Text[] = []; for (let i = primaryBlockIndex + 1; i < siblings.length; i++) { const node = siblings[i]; @@ -569,11 +572,10 @@ class HtmlAstToIvyAst implements html.Visitor { continue; } - // Ignore empty text nodes between blocks. if (node instanceof html.Text && node.value.trim().length === 0) { - // Add the text node to the processed nodes since we don't want - // it to be generated between the connected nodes. - this.processedNodes.add(node); + // Collect whitespace-only text nodes; only mark them as processed once we confirm + // they precede a connected block (e.g. @else / @else if). + pendingTextNodes.push(node); continue; } @@ -582,6 +584,12 @@ class HtmlAstToIvyAst implements html.Visitor { break; } + // A connected block was found — commit the pending whitespace nodes as processed. + for (const pending of pendingTextNodes) { + this.processedNodes.add(pending); + } + pendingTextNodes.length = 0; + relatedBlocks.push(node); this.processedNodes.add(node); } diff --git a/packages/compiler/test/render3/r3_template_transform_spec.ts b/packages/compiler/test/render3/r3_template_transform_spec.ts index 59ac9b364e9..e92196a36f5 100644 --- a/packages/compiler/test/render3/r3_template_transform_spec.ts +++ b/packages/compiler/test/render3/r3_template_transform_spec.ts @@ -2636,6 +2636,58 @@ describe('R3 template transform', () => { ]); }); + it('should preserve &ngsp; between two sibling @if blocks', () => { + expectFromHtml( + `@if (true) {Hello}&ngsp;@if (true) {World}`, + ).toEqual([ + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'Hello'], + ['Text', ' '], + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'World'], + ]); + }); + + it('should preserve plain whitespace between two sibling @if blocks when preserveWhitespaces is enabled', () => { + expectFromR3Nodes( + parse(`@if (true) {Hello}\n@if (true) {World}`, { + preserveWhitespaces: true, + }).nodes, + ).toEqual([ + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'Hello'], + ['Text', '\n'], + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'World'], + ]); + }); + + it('should preserve   between two sibling @if blocks when preserveWhitespaces is enabled', () => { + expectFromR3Nodes( + parse(`@if (true) {Hello} @if (true) {World}`, { + preserveWhitespaces: true, + }).nodes, + ).toEqual([ + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'Hello'], + ['Text', '\u00A0'], + ['IfBlock'], + ['IfBlockBranch', 'true'], + ['Element', 'span'], + ['Text', 'World'], + ]); + }); + describe('validations', () => { it('should report an if block without a condition', () => { expect(() =>