Component inside component encapsulate problem

🐞 bug report

Is this a regression?

No

Description

When using custom accordion component and put another accordion component inside the accordion component, Angular encapsulating does not work. I have svg arrow there and it has same property (f.e. _ngcontent-how-c51) in inner accordion than outer accordion. (here is some blog post about properties: https://blog.angular-university.io/angular-host-context/)

The main problem is that the inner accordion’s arrow does not rotate.

🔬 Minimal Reproduction

https://stackblitz.com/edit/angular-fb9s2c?file=src%%2Fapp%%2Fapp.component.html You may see, that in the demo, the inner accordion arrow does not rotate. I believe it is something to do with encapsulating.

🌍 Your Environment

I created new project with ng new.

Anything else relevant?
Main issue here is how to manipulate the svg arrow to rotate in situation described in stackblitz linked above. There are two nested custom components and it seems it is impossible to use css (and :host-context) to manipulate the svg arrow so it would rotate in inner accordion and outer accordion. With different scss it is possible to rotate the inner accordion’s arrow, but then the outer accordion arrow does not rotate.

This rotates inner accordion’s arrow:

:host-context(.accordion-div .is-expanded) img {
transform: rotate(180deg);
}

This rotates outer accordion’s arrow:

:host-context(.is-expanded) img {
transform: rotate(180deg);
}

If one puts both there still both arrows does not rotate.

2 thoughts on “Component inside component encapsulate problem

  1. So I think the problem you have is that :host-context does not care about encapsulation. For example:

    :host-context(.is-expanded) img { transform: rotate(180deg); }

    This rule says that if there is “any” element that is an ancestor to the current component that has the is-expanded class, then rotate the img element.

    So in HTML that looks like:

    <div class="accordion-div is-expanded">
      <accordion-pane-header><img id="img1"></accordion-pane-header>
      <accordion-pane-content>
        <div class="accordion-div not-expanded">
          <accordion-pane-header><img id="img2"></accordion-pane-header>
          ...
        </div>
      </accordion-pane-content>
    </div>

    Both img1 and img2 will be rotated since even img2 will see the is-expanded div two levels above.

  2. I was going to post something similar, but @petebacondarwin beat me to it 😁

    Since :host-context() matches any of the root’s ancestors (all the way up to the document element), a selector that looks like :host-context(.foo) img will match an <img> tag if any of its ancestors has a .foo class.

    So, you need a selector that will be able to tell whether the owning accordion-pane (and not any of its ancestors) is opened or not. One way to achieve this would be to add the .is-expanded class to the immediate parent of the <img> tag based on the parent AccordionPaneComponent‘s isOpened property.

    You can change accordion-pane-header.component.ts like this:

    -<div class="svg">
    +<div class="svg" [class.is-expanded]="accordionPane.isOpened">
       <img src="assets/svg/sort-down.svg" />
     </div>

    And then change accordion.component.scss like this:

    -:host-context(.accordion-div .is-expanded) img {
    +.is-expanded > img {
       transform: rotate(180deg);
     }

    I am going to close the issue (since there doesn’t seem to be anything to fix in Angular), but feel free to continue the discussion below.