Skip to content

Nested & selectors inside :host get _ngcontent attribute and no longer match (regression in 22.2.0) #71050

Description

@anhallbe

Which @angular/* package(s) are the source of the bug?

compiler

Is this a regression?

Yes

Description

Since d0d7f57 (#69885, "fix(compiler): scope nested CSS rules"), selectors nested with & inside a :host rule are scoped with the content attribute. & there refers to the host element, which only has the _nghost-* attribute, so the rule never matches.

@Component({
  selector: 'app-avatar',
  host: { class: 'md' },
  template: '',
  styles: `
    :host {
      display: block;
      &.md { width: 70px; }
    }
  `,
})
export class Avatar {}

Output of the emulated encapsulation:

Version Output
22.1.8 [_nghost-c1] { display: block; &.md { width: 70px; } }
22.2.0 [_nghost-c1] { display: block; &.md[_ngcontent-c1] { width: 70px; } }

In 22.2.0 the rule resolves to [_nghost-c1].md[_ngcontent-c1]. The host element carries _nghost-c1 and its parent's _ngcontent-*, so &.md no longer applies.

Minimal repro with the compiler directly:

import { encapsulateStyle } from '@angular/compiler';
encapsulateStyle(':host { &.md { width: 70px } }', 'c1');
// 22.1.8: [_nghost-c1] { &.md { width: 70px } }
// 22.2.0: [_nghost-c1] { &.md[_ngcontent-c1] { width: 70px } }

Expected: a compound selector that starts with & directly under :host (e.g. &.md, &:hover, &[attr]) is left unscoped, or scoped with the host attribute. Descendant selectors (& .child, .child) should still get the content attribute.

packages/compiler/test/shadow_css/nesting_spec.ts covers :host(.foo) { .child {} } and .parent { &.modifier {} }, but not :host { &.modifier {} }.

Please provide a link to a minimal reproduction of the bug

https://stackblitz.com/edit/bhxywn3c?file=src%2Fmain.ts

Please provide the exception or error you saw

None. The styles silently stop applying.

Please provide the environment you discovered this bug in (run ng version)

Angular CLI       : 22.2.0
Angular           : 22.2.0
Node.js           : 24.16.0
Package Manager   : npm 11.13.0
Operating System  : darwin arm64

┌───────────────────────────────────┬───────────────────┬───────────────────┐
│ Package                           │ Installed Version │ Requested Version │
├───────────────────────────────────┼───────────────────┼───────────────────┤
│ @angular/animations               │ 22.2.0            │ ~22.2.0           │
│ @angular/build                    │ 22.2.0            │ ~22.2.0           │
│ @angular/cdk                      │ 22.2.0            │ ~22.2.0           │
│ @angular/cli                      │ 22.2.0            │ ~22.2.0           │
│ @angular/common                   │ 22.2.0            │ ~22.2.0           │
│ @angular/compiler                 │ 22.2.0            │ ~22.2.0           │
│ @angular/compiler-cli             │ 22.2.0            │ ~22.2.0           │
│ @angular/core                     │ 22.2.0            │ ~22.2.0           │
│ @angular/forms                    │ 22.2.0            │ ~22.2.0           │
│ @angular/platform-browser         │ 22.2.0            │ ~22.2.0           │
│ @angular/platform-browser-dynamic │ 22.2.0            │ ~22.2.0           │
│ @angular/router                   │ 22.2.0            │ ~22.2.0           │
│ ng-packagr                        │ 22.2.2            │ ^22.2.2           │
│ rxjs                              │ 7.8.2             │ ^7.8.2            │
│ typescript                        │ 6.0.3             │ ~6.0.3            │
│ vitest                            │ 5.0.2             │ ^5.0.2            │
│ zone.js                           │ 0.16.3            │ ~0.16.0           │
└───────────────────────────────────┴───────────────────┴───────────────────┘

Anything else?

We JIT-compile third-party components at runtime, so their authors hit this without being able to change the build.

The repro uses aot: false. In our AOT CLI build, the CSS pipeline flattened :host { &.on {} } to :host.on {} before ShadowCss ran, which hides the bug. It shows up whenever unflattened nested CSS reaches the compiler, e.g. JIT.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions