Skip to content

ViewEncapsulation not working on nested components with ng-content #7400

Closed

Description

@fxck

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?

Activity

  1. thomashilzendegen commented on Mar 3, 2016

    @thomashilzendegen

    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.

  2. zoechi commented on Mar 3, 2016

    @zoechi
    Contributor

    With this selector :host::content a { the link becomes green. I expected ::content alone to work but AFAIR there is the same issue in shimmed shadow DOM in Polymer.
    It also works with encapsulation: ViewEncapsulation.None

  3. thomashilzendegen commented on Mar 3, 2016

    @thomashilzendegen

    Ah, nice! This is a good catch with ":host::content" but ":host" is enough. This scopes the "a" and the shim does not trigger for it, as it is already scoped.

  4. rvalimaki commented on Sep 12, 2016

    @rvalimaki

    I'm quite sure this was already working on ie. RC.4 (and also in all older versions we did actually use), but broken in RC.6.

    I did make a quick Plunker about the situation and my problem:

    https://plnkr.co/edit/R3agaCYIVWK6UnscMEwA?p=preview

    I also tried the :host::content syntax suggested by @zoechi , but as you can see from the Plunker, it doesn't change a thing.

    This is a new regression, since our code worked perfectly regarding this in RC.4, but no more in RC.6 (finished the RC.4 -> RC.6 update last Friday).

    I can overcome the situation using ViewEncapsulation.None, but I really, really do not want to do that, since that would also affect siblings and parents, which is AFAIK the specific reason why to use some view encapsulation in the first place.

  5. rvalimaki commented on Sep 13, 2016

    @rvalimaki

    Okay, as tested, this original issue #7400 was invalid when it was created (3rd March), but now the issue is real for RC.6 and RC.7, probably also for RC.5, but I haven't tested it. In RC.4 and older this worked., but is not working anymore.

    This is a modified plunker from the original post, with ":host" added, and as you can see, it did work perfectly (beta.7):
    http://plnkr.co/edit/UNSjGDYB04S3lpWScKgf?p=preview

    I can also assure you that it did work for RC.4 as well.

    However, as you can see from my recent plunker (RC.6), this is not working anymore:
    https://plnkr.co/edit/R3agaCYIVWK6UnscMEwA?p=preview

    Should I open a new bug report on this (since this is a new regression / new feature change), or is anyone anywhere inside Angular2 team going to even see this comment here, and acknowledge the possibly nasty bug (or even nastier feature change...)?

  6. self-assigned this
    on Sep 13, 2016
  7. vicb commented on Sep 13, 2016

    @vicb
    Contributor

    Saw it. If think this is the correct behavior. Let me double check and explain when I'm at work in a bit

  8. vicb commented on Sep 13, 2016

    @vicb
    Contributor

    With shadow DOM projected content keep the style at their original location.

    Works as expected.

  9. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @vicb Thanks for your quick response, and sorry to hear that.

    While this might be the correct way according to shadow DOM emulation, this limits the usefulness of the (and ), since direct children for component works now differently than indirect children.

    To overcome the issue, you can:
    A) Set parent component encapsulation to ViewEncapsulation.None
    B) Use LESS or similar (otherwise will be a major PITA) for generating CSS
    C) replace ":host { /* all your styles here for component and children / }"
    with "parent-component-tag-name { /
    all your styles here for component and children */ }"

    With above three-step process, the component styling works pretty much similar to what it was for all Angular2 versions from alpha & beta to RC.4,

    Caveat:
    You better not forget to prefix your styles with "parent-component-tag-name", and you better not use the same component selector twice for different components.

    Probably you could think about a more convenient way to style components (or native HTML elements!) inside the , without totally breaking the encapsulation.

  10. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @vicb

    I just educated myself about Shadow DOM and it seems that we probably should have a "deep" combinator (previously "/deep/" in v0, currently ">>>" in v1). That would be a direct solution for my problem.

    I'll try the >>> selector, and if it's not working with ViewEncapsulation.Emulated, I'll open a new bug report for Angular2.

    https://drafts.csswg.org/css-scoping-1/#deep-combinator

  11. zoechi commented on Sep 14, 2016

    @zoechi
    Contributor

    In shadow DOM the ::content selector was used to address transcluded content.
    What I figured out to work is ::content in combination with >>>

    https://plnkr.co/edit/fhaOzBmIVKkxVGshOzp4?p=preview

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

    >>> shouldn't be necessary here.
    See also http://stackoverflow.com/questions/37763252/custom-styling-on-ng-content-in-angular2-not-working/37764548#37764548

  12. vicb commented on Sep 14, 2016

    @vicb
    Contributor

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

    @zoechi ::content is ignored by the shim.

  13. vicb commented on Sep 14, 2016

    @vicb
    Contributor

    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.

  14. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @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 */ }

  15. zoechi commented on Sep 14, 2016

    @zoechi
    Contributor

    @vicb I see. Thanks for the update.

  16. rvalimaki commented on Sep 14, 2016

    @rvalimaki

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

    Now you lost me completely. According to all sources I can find, the "Shadow-Piercing descentant combinator" should do exactly that for Shadow DOM, which is, piercing the shadow for all descendants.

    If you don't need to pierce shadow DOM, then you simply don't need the >>> in the first place. But it's not a bug or limitation, that shadow piercing descendant combinator pierces the shadow for descendants. And I will definitely rely on this feature.

  17. vicb commented on Sep 14, 2016

    @vicb
    Contributor

    "Shadow-Piercing" pierces ... (suspens) ... the shadow, not the projected content.

    read the 101/201.

  18. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @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 ."

    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.

  19. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    "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 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".

  20. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @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!

  21. zoechi commented on Sep 14, 2016

    @zoechi
    Contributor

    @rvalimaki the shadow piercing combinators were deprecated because the mere existence prevents some performance optimization strategies that shadow DOM boundaries allow.

    The proposed replacement are CSS variables (not yet supported by Angular ViewEncapsulation.Emulated) and hopefully CSS mixins as Polymer provides them as shims.

    The >>> and /deep/ are mostly a workaround for the missing CSS variables shims (CSS variables alone are too weak though anyway).

  22. rvalimaki commented on Sep 14, 2016

    @rvalimaki

    @zoechi

    Thanks, looking forward for get those various Shadow DOM styling features implemented in Angular2, including ::slotted(). But already the ::slotted() is only a limited version of ::content, allowing reference only to top level elements of "slot" (or "ng-content" in Angular2 vocabulary), instead that all elements as ::content was able to do. I guess that performance might be the reason for ::slotted() being very limited on focus.

    In addition to #11595 issue I opened about ::slotted(), there is already issues #10663 and #2567 open about CSS variable shimming and style hooks.

  23. mcchae7 commented on Apr 19, 2017

    @mcchae7

    I have the same issue in Angular 4.0.2.
    Do you know how can I fix this?
    Reproduce: http://plnkr.co/edit/wg6xSs0wsulVg8n1QzcB?p=preview

  24. rvalimaki commented on Apr 19, 2017

    @rvalimaki

    @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>
    
  25. angular-automatic-lock-bot commented on Sep 11, 2019

    @angular-automatic-lock-bot

    This issue has been automatically locked due to inactivity.
    Please file a new issue if you are encountering a similar or related problem.

    Read more about our automatic conversation locking policy.

    This action has been performed automatically by a bot.

  26. locked and limited conversation to collaborators on Sep 11, 2019
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions