Skip to content

Conversation

@ryan-bendel
Copy link
Contributor

This fixes an issue where a child component inside of IsolatedShadowDom would get it's styles moved to the root style object, causing it to be unstyled. This new approach hoists the child styles to the shadowroot. This change only applies to IsolatedShadowDom

PR Checklist

Please check if your PR fulfills the following requirements:

PR Type

What kind of change does this PR introduce?

  • Bugfix
  • Feature
  • Code style update (formatting, local variables)
  • Refactoring (no functional changes, no api changes)
  • Build related changes
  • CI related changes
  • Documentation content changes
  • angular.dev application / infrastructure changes
  • Other... Please describe:

What is the current behavior?

After introducing IsolatedShadowDom, it was raised on the PR that any child components using other encapsulation methods would have their styles hoisted to the root styles of the app. This would cause the child component to be unstyled as it is inside a shadowroot.

What is the new behavior?

The new behaviour hoists the styles of any child component into the head of the shadowroot.

Does this PR introduce a breaking change?

  • Yes
  • No

Other information

Spun up a test project:

  1. Set a global button colour, green.
  2. Created an isolated shadowdom component, with a button, and a child component using emulated, with a button styled red

Comparisons:
Standard/old Shadowdom encapsulation with style leakage:

Screenshot 2025-08-13 at 08 09 09

IsolatedShadowDom pre-fix (styles are moved to root so child component loses styling):

Screenshot 2025-08-13 at 08 14 58

IsolatedShadowDom post-fix (styles are moved to shadowhost, fixing child component styling):
Screenshot 2025-08-13 at 08 09 23

As you can see from the last screenshot, the styles are now correctly applied to the child (Emulated) component

@angular-robot angular-robot bot added detected: feature PR contains a feature commit area: core Issues related to the framework runtime labels Aug 13, 2025
@ngbot ngbot bot added this to the Backlog milestone Aug 13, 2025
@ryan-bendel
Copy link
Contributor Author

ryan-bendel commented Aug 13, 2025

@thePunderWoman et al 👋 - me again!

A follow up to the IsolatedShadowDom PR we did recently, it was raised that there was an issue with child components inside an IsolatedShadowDom component losing their styling (as sharedStylesHost was removed)

I've done a bit of a refactor, providing an alternative way for IsolatedShadowDom to add/hoist styles, which fixes the issue.

This is very much a RFC, or even better if you have capacity to have a quick test? It's a bit of a grey area for me in regards to the shadowdom spec, which doesn't explicitly talk about child component styling (and Angular muddies the waters a bit with different encapsulation methods). However this feels like the best of both worlds, parent styles cannot leak/be added into the shadowdom, but child component styles are correctly applied.

@ryan-bendel ryan-bendel changed the title feat(platform-browser): Fixes IsolatedShadowDom encapsulation method fix(platform-browser): Fixes IsolatedShadowDom encapsulation method Aug 13, 2025
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch 2 times, most recently from 5039a7e to 367d39d Compare August 13, 2025 10:37
@ryan-bendel ryan-bendel marked this pull request as ready for review August 15, 2025 10:10
@pullapprove pullapprove bot requested a review from AndrewKushnir August 15, 2025 10:11
@AndrewKushnir AndrewKushnir requested review from dgp1130 and thePunderWoman and removed request for AndrewKushnir August 15, 2025 16:40
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from bba0484 to 8813776 Compare August 18, 2025 11:38
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 0b8d24e to 42511ea Compare August 19, 2025 13:15
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 42511ea to 19ea9b8 Compare September 13, 2025 06:45
@ryan-bendel
Copy link
Contributor Author

👋 - any chance of getting this merged (stops me having to keep the branch up to date) 😅 ? Would be good to get this in a 21-next release

@alan-agius4 alan-agius4 requested review from crisbeto and removed request for dgp1130 September 13, 2025 07:24
@alan-agius4 alan-agius4 added the action: review The PR is still awaiting reviews from at least one requested reviewer label Sep 13, 2025
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from a0c6ebe to 3b5f324 Compare September 13, 2025 08:19
@thePunderWoman
Copy link
Contributor

👋 - any chance of getting this merged (stops me having to keep the branch up to date) 😅 ? Would be good to get this in a 21-next release

We're hoping to take a deeper look at it soon! Thanks for the effort keeping this up to date.

Copy link
Contributor

@dgp1130 dgp1130 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies for the slow review @ryan-bendel, thanks for pushing through this.

Just some minor comments, main thing is just trying to understand the lifecycle of SharedStylesHost and how shadow roots are tracked there.

@ryan-bendel
Copy link
Contributor Author

ryan-bendel commented Sep 20, 2025

@dgp1130 @thePunderWoman - Addressed the comments on the PR. I did encounter a bug though, which meant i've had to do a bit of a refactor (and arguably add a bit more complexity)

I had only tested a single IsolatedShadowDom with child emulated/none components, that worked fine. The issue was, if the same emulated component was introduced elsewhere in the app as well, the styles were hoisted out of the shadowroot, into the app styles, breaking the ui inside IsolatedShadowDom.

My fix now tracks usage, and copies the styles into both app & shadowroot, so styles persist across usages.

The one "gotcha", which I don't think we need to fix (i think it'll exist today) - is if you put a "none" encapsulation component inside a shadowdom, you get into css specificity issues, as there is not a unique identifier in ShadowDom/None like there is with Emulated. Hard to articulate exactly what i mean, hopefully the below image helps. These components are all <p> tags with a color assigned, so IsolatedShadowDom should be blue get's overridden (there's two style's now for p) - I really don't know if there's a fix for this, or even if it should be fixed.

Lastly, i'm unable to really test SSR in practice (i don't really use it so don't feel qualified enough to thoroughly test it) - if one of you would have any capacity to check there's no regressions (even outside of SSR), that would be much appreciated! The more i touch this the less confident i get 🤣

Screenshot 2025-09-20 at 10 01 02

@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 1dabb83 to 34391f7 Compare September 22, 2025 15:09
@atscott atscott modified the milestones: Backlog, v21 Candidate Sep 22, 2025
Copy link
Contributor

@dgp1130 dgp1130 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wouldn't worry too much about SSR given that we don't support declarative shadow DOM just yet. AFAICT, it's just broken today as we seem to put component styles into the <head> tag, which is necessary for initial render but also leaks those styles out of the shadow root.

IsolatedShadowRoot should probably throw when used in SSR until we support declarative shadow DOM. So should ShadowRoot frankly, but I'll check with the rest of the team to see if that's worth the breaking change at the moment.

@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 39be8de to f0a0dc5 Compare October 3, 2025 09:42
@ryan-bendel ryan-bendel requested a review from dgp1130 October 3, 2025 10:36
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 7755745 to f8deb43 Compare October 3, 2025 11:45
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 9f3ae01 to a4d94cd Compare October 4, 2025 08:18
@dgp1130
Copy link
Contributor

dgp1130 commented Oct 10, 2025

Thanks for the follow up, I haven't had a chance to look at it just yet, but FYI, I'm gonna be out for a couple weeks and will look at this when I get back.

this.shadowRoot = (hostEl as HTMLElement).attachShadow({mode: 'open'});
} else {
// In SSR or environments without shadow DOM support, throw
throw new Error(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: Won't this throw for standard ViewEncapsulation.ShadowDom? I think we need to maintain the existing behavior and only throw for IsolatedShadowDom.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@dgp1130 - maybe i'm misremembering, but standard Shadowdom doesn't work in SSR either, right? Which is what this would catch. I could put in a condition to only throw for Isolated, but i'm not clear on what standard would do in a server environment here....

@JeanMeche
Copy link
Member

Looks like I found a usecase on angular.dev for that feature once the fix lands
By any change could you rebase the PR on main and take into account Doug's comments.
Thank you.

@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch 2 times, most recently from 1439753 to 94d7b2e Compare December 1, 2025 07:45
@ryan-bendel
Copy link
Contributor Author

ryan-bendel commented Dec 1, 2025

Looks like I found a usecase on angular.dev for that feature once the fix lands By any change could you rebase the PR on main and take into account Doug's comments. Thank you.

@JeanMeche @dgp1130

I've refactored IsolatedShadowDom now to use native slot and disallow ng-content as discussed. This definitely simplified things and fixes any wrapped child/projected content issues.

Removed dom walking in isolated_style_scope and replaced with using Angulars LView hierarchy which is much nicer.

Demo if you want to have play (if you uncomment ng-content it should generate an error)

@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 032f709 to 8530d0e Compare December 1, 2025 09:04
"dist/main.js": 153915,
"dist/polyfills.js": 34023,
"dist/open-close.component-[hash].js": 1190
"dist/main.js": 159053,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since this is quite an increase, we should investigate better tree shaking when the feature isn't used

Copy link
Contributor Author

@ryan-bendel ryan-bendel Dec 1, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I shall take a look @JeanMeche - but i am struggling to run the integration tests locally (i bumped all the size.json from the output in the gh test runner output),

I get:

ERROR  Failed to switch pnpm to v10.23.0. Looks like pnpm CLI is missing at "/var/folders/kj/cqmyhbyx0zv7mrtvpdlj19dw0000gp/T/ng-integration-test7nTJJM/tmp-env-0/Library/pnpm/.tools/pnpm/10.23.0/bin" or is incorrect
spawnSync /var/folders/kj/cqmyhbyx0zv7mrtvpdlj19dw0000gp/T/ng-integration-test7nTJJM/tmp-env-0/Library/pnpm/.tools/pnpm/10.23.0/bin/pnpm ENOENT
Command failed: `pnpm install`

🤔

@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 2c2deab to 60acf6d Compare December 1, 2025 09:40
@ryan-bendel ryan-bendel force-pushed the fix-isolated-shadowdom branch from 7cdd447 to c0d2fb9 Compare December 1, 2025 11:52
Implement IsolatedStyleScopeService. Refactor IsolatedShadowDom implementation to fix various bugs. Uses native slot instead of ng-content.
@dgp1130
Copy link
Contributor

dgp1130 commented Dec 13, 2025

Sorry for the slow responses on my end, I've been meaning to take another look here but have just been held up.

FYI, I made a related PR #66048 to fix bootstrapping Angular inside of a shadow root and found myself rewriting most of the styling system to better support ViewEncapsulation.IsolatedShadowDom and incorporating many of the ideas from our conversation in this PR. When I take another look, I'll try to figure out if it's still worth landing this PR independently and potentially rebasing mine on top of it (or vice versa depending on which lands first).

@dgp1130
Copy link
Contributor

dgp1130 commented Dec 20, 2025

Looking at this PR again, I'm thinking the changes to shared_styles_host and dom_renderer and mostly obsoleted by #66048 which achieves much of the same result (unless you see something I'm not addressing there).

Where there is still value is in the changes around that. Specifically the errors for running IsolatedShadowDom in SSR or using <ng-content /> with it, both of which are not included in my PR and are worth landing independently. I'm thinking it might be worth closing this PR and splitting into a couple others for those more specific features and potentially more tests for the new constraints of IsolatedShadowDom which I probably did not exhaustively cover.

@ryan-bendel
Copy link
Contributor Author

Looking at this PR again, I'm thinking the changes to shared_styles_host and dom_renderer and mostly obsoleted by #66048 which achieves much of the same result (unless you see something I'm not addressing there).

Where there is still value is in the changes around that. Specifically the errors for running IsolatedShadowDom in SSR or using <ng-content /> with it, both of which are not included in my PR and are worth landing independently. I'm thinking it might be worth closing this PR and splitting into a couple others for those more specific features and potentially more tests for the new constraints of IsolatedShadowDom which I probably did not exhaustively cover.

@dgp1130 Apologies for the slow reply, i was having a bit of a digital detox over the holidays, so just catching up!

Happy to close this, if you could tag me in the other PR when it gets merged I can then start again with anything that's missing for Isolated 😄

@ryan-bendel
Copy link
Contributor Author

Closing awaiting #66048 to be merged to pick up again

@ryan-bendel ryan-bendel closed this Jan 7, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action: review The PR is still awaiting reviews from at least one requested reviewer area: core Issues related to the framework runtime detected: feature PR contains a feature commit

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants