angular: ViewEncapsulation not working on nested components with ng-content

I’m not actually sure what is causing the problem, but I have two components

  • list-cmp
    • list-item-cmp

both have only ng-content inside them, and I’m using it like this

    <list-cmp>
      <list-item-cmp>
        <a href="">whatever</a>
      </list-item-cmp>
    </list-cmp>

when I try to style the anchor with styles on list-item-cmp, it doesn’t work

image

http://plnkr.co/edit/tdIIXsb2QA7oAlJIFXg1?p=preview

is it a bug, is it by design?

About this issue

  • Original URL
  • State: closed
  • Created 8 years ago
  • Comments: 24 (9 by maintainers)

Most upvoted comments

@vicb I opened a new issue (#11595) about supporting ::slotted() (old name was ::content) selector, which would help over the issue without completely ditching the view encapsulation. In the meantime, we will use ViewEncapsulation.None for those container components.

Thanks for your good work on Angular2, I appreciate it a lot!

@vicb:

For example: http://www.html5rocks.com/en/tutorials/webcomponents/shadowdom-201/

"The /deep/ combinator

The /deep/ combinator is similar to ::shadow, but more powerful. It completely ignores all shadow boundaries and crosses into any number of shadow trees. Put simply, /deep/ allows you to drill into an element’s guts and target any node.

The /deep/ combinator is particularly useful in the world of Custom Elements where it’s common to have multiple levels of Shadow DOM. Prime examples are nesting a bunch of custom elements (each hosting their own shadow tree) or creating an element that inherits from another using <shadow>."

Simply put: “Put simply, /deep/ allows you to drill into an element’s guts and target any node.”

I tried to do several google searches about the subject, but I found nothing about shadow-piercing not affecting projected content. I think that’s simply because shadow-piercing “completely ignores all shadow boundaries and crosses into any number of shadow trees”

But alas, the battle is lost already. Google Chrome’s team has failed us, and proposed that the shadow-piercing was a mistake, and should be discarded anyway. And actually their proposal won, so shadow-piercing is now officially deprecated, and will be removed at some point. Sad, but true.

So the “correct” way to overcome this sad situation is to just ditch the shadow dom completely for components, that are meant for containers, such as custom “table” component. With shadow-piercing deprecated, you can no more style eg. “tr” and “td” elements in your custom table component in a sane way, instead of completely ditching the view encapsulation, and thus not using HTML-compatible native components in the future.

There might be good performance / security / whatever related reasons why Google and others are ditching the shadow-piercing, but the cost in the developer end is also high. Sure, you can then introduce eg. “tr-that-should-be-used-in-that-particular-custom-table-component” -component, and “td-that-should-be-used-in-that-particular-custom-tr-for-that-particular-custom-table-component”, but really, what a huge pain. And what about having a 2-tone background colors for every second “custom-tr” in that particular “custom-table”?

But anyway, this is not Angular2 related anymore.

@zoechi, @vicb

Thanks! That’s perfect.

Instead of ::content, we should use :host. Otherwise encapsulation doesn’t work.

so the correct syntax is:

styles: [ :host >>> h1 { background: yellow !important; font-size: 10px !important; }],

And if there are fellow LESS users, seems that LESS compiler dislikes >>> syntax, so you need to add an alias for that, eg. '@deep: ~“>>>”; ’ and then use that like '@{deep} { /* your deep styles here */ }

@rvalimaki both >>> and /deep/ work to reach nested components.

@zoechi ::content is ignored by the shim.

@mcchae7

Until bug / missing feature #11595 is fixed, the only way to fix this is using ViewEncapsulation.None. Please see modified plunker (listitem.ts):

http://plnkr.co/edit/yO9SwBrBaUjY1SoTwFaf?p=preview

Otherwise this issue #7400 is closed as it works as advertised, which is sad, but beyond ng2 team to fix. It’s the way web components are currently supposed to work, even if it feels a bit brain dead at the first sight, to be honest. I do believe that they had good (performance related) reasons for that decision in W3C though, so this will never be “fixed”.

When #11595 is fixed (not holding my breath…) to match current web components standard, that’ll help on simple situation like in your plunker, though. That is, addressing to direct children inside <ng-content> should be possible using ::slotted(), though it’s not yet implemented. You cannot address other ancestors that way though.

<ng-content>
  <div>You should be able to address this div using ::slotted() syntax
    <div>...but this nested div you cannot access with ::slotted(), sorry!</div>
  </div>
</ng-content>

“But anyway, this is not Angular2 related anymore.”

Though in a way, that could be Angular2 related. If we only did get a really clever <ng-content> selectors that could allow you to do following:

<custom-container>
  <name>x</name> value>1</value>
  <name>y</name><value>2</value>
  <name>z</name><value>3</value>
  ...
 <name>n+1</name><value>n+1</value>
</custom-container>

And then in custom-container template:

<ng-content-for=['name', 'value']>
<tr>
   <td class="name"><ng-content select="name"></ng-content></td>
   <td class="value"><ng-content select="value"></ng-content></td>
</tr>
</ng-content-for>

… or something similar. That would get us much further, without piercing the shadow. But I guess the limits of current “ng-content” are derived from the limitations of native components “content”.

that being said >>> is not supposed to style the projected content in the native implementation.

it will for in emulated mode because of a limitation that we might fix at some point.

so best is to style your projected content in its parent component as you would do with the native impl. You can check “shadow DOM 101, …” articles - they are about v0 but concepts are still valid.

AFAIK the style compiler flags all element selectors with the current component variable to ensure that no outside element is affected by the component’s style. The problem here is, that ngContent is using your HTML as is and no additional attribute would be added to those elements (to match to the generated CSS).

But as you are already somehow “scoping” your CSS by yourself you could switch your component to view encapsulation mode “None” (encapsulation: ViewEncapsulation.None) to skip the shimming of the CSS.