ViewEncapsulation.Emulated is not working for dynamically created components
See original GitHub issueI’m submitting a … (check one with “x”)
[x] bug report => search github for a similar issue or PR before submitting
[ ] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
Current behavior
Angular 2 doesn’t set _ngcontent-*
attribute on the components created using ViewContainerRef.createComponent()
when ViewEncapsulation.Emulated
is used.
Expected behavior
Angular 2 sets _ngcontent-*
attribute on the components created using ViewContainerRef.createComponent()
when ViewEncapsulation.Emulated
is used.
Minimal reproduction of the problem with instructions
~See reproduction example: http://plnkr.co/edit/0HF3A2muRlAVOEonJvs7~ Updated reproduction for Angular 6: https://stackblitz.com/edit/angular-uzjtlp
If you change view encapsulation to native (in browser supporting it) my-dynamic-comp
will become red (which is expected behaviour). But with emulated view encapsulation it stays green. It’s because _ngcontent-*
attribute is not set on the dynamically created component’s host element.
What is the motivation / use case for changing the behavior?
Emulated behaviour works different from native, so I assume it is a bug.
Please tell us about your environment:
- Angular version: 2.0.2
- Browser: all
- Language: TypeScript (but it may be relevant for others as well)
Issue Analytics
- State:
- Created 7 years ago
- Reactions:16
- Comments:9 (5 by maintainers)
@mlc-mlapis I have added the StackBlitz URL to the original issue using Angular 6. It does not work exactly the same as before.
@Ettapp This is not enough. The element with
_nghost-c4
also should have_ngcontent-*
from the parent component, which it doesn’t.PS You can use
*
selector instead of exact component name if you want styles to apply to any element. E.g.:host .container ::ng-deep > * {}
, it will apply styles to any dynamic component located inside element withcontainer
class.Custom directive + ComponentPortal solution:
I find using
ComponentPortal
the best way to generate components, and you then attach them easily to anng-template
element. If you’re not already using it I recommend it for simplicity.Creating a ComponentPortal is quite easy:
Then you render it like this:
You can inject dependencies by the mechanism described here. (Note for > Angular 10 you shouldn’t use the deprecated answer with WeakMap).
Important Design / Injector Tree considerations
You may just be creating one dynamic component, or you may be creating a whole tree. In either case you really need to pass the injector of your host component into the
ComponentPortal
constructor to get the ‘normal’ behavior you’d expect in a component injector tree.Oddly the example shown above (from CDK docs) doesn’t do this. I think the reason is one of the primary uses for portals is to take a component defined one place and put it on your page wherever you want. So in that case a parent injector makes less sense.
If you’re generating a component dynamically and placing it in the same component you really should be using the following constructor:
However if you’re creating a tree of dynamic components this becomes a logistical pain! You have to clutter up your host components with all this parentInjector code.
My solution to ViewEncapsulation.Emulated issue
My application is a graphical UI to design a page from components like grids, tables, images, video etc.
The model is defined as a tree of ‘rendered nodes’ something like the following. As you can see I have a
ComponentPortal
in each node:BTW. This model is displayed by a component that iterates through
children
and recursively calls itself to stamp out the tree. Basically it’s an*ngFor
loop ofng-template [cdkPortalOutlet]="node.portal"
.I started by (naively) creating all the
ComponentPortal
for the tree eagerly. The problem with this way is that the correct component instance injector isn’t available at the time I create the tree. When you create aComponentPortal
your component is not actually instantiated. This means that component injected services - notablyRenderer2
aren’t the one you actually would want. In fact when I tried@SkipSelf() private renderer2: Renderer2
it would jump all the way to the outermost dynamic component.So I realized I’d need to avoid creating the component portal until the actual host component was being ‘run’:
This is what the original attempt looked like (with eagerly created portalInstances):
Then I realized I could just make my own portal directive to do exactly what I wanted and more!
Note how I pass in the node and not the portal instance.
So what this directive will do is:
pagenode
representing the dynamic component’s definition only (and its children)ComponentPortal
instance with the correct parent InjectordynamicComponentOutlet
outlet is the host component it can also generate and apply the_ngcontent-app-c338
attribute (which is the whole issue this issue is about!).Here’s my solution:
LazyComponentOutlet
which contains a placeholder for theComponentPortal
and also any data needed to create it. I’ve just called thisparams
because it will be up to you. I’m also not including theComponentPortalParams
definition for the same reason. At minimum it would need to include the component type.Then the
DynamicComponentPortalHost
attribute (rename this however you please):Note this is inspired by the way they do portal inheritance in portal-directives.ts
Finally this method works just as well for a single item and not a tree. Or you could extract just the part that renders the
ngContent
attribute if you can’t shoehorn this into an existing project.And that’s it! Of course if they fix (or change encapsulation) this in future you’ve only got to update this in one place.