'Render nested components without host element container - Separate SVG parts into Different components
I have a component with an SVG element, I am trying to separate the parts of that SVG into different components. The nested components need to be created dynamically from the parent component, this is because of the fact I am building the whole SVG graphic from a json file that determines which nested components (SVG nested shapes) need to be added.
To create the components dynamically I am using ComponentFactoryResolver and the ViewContainerRef from @angular/core
@Component({
selector: 'skick-player',
templateUrl: './player.component.html',
styleUrls: ['./player.component.scss'],
})
export class PlayerComponent implements OnInit, AfterViewInit {
@ViewChild('svgContainer', { read: ViewContainerRef }) container;
constructor( private resolver: ComponentFactoryResolver) {}
performFrame() {
....
//Add nested component
const factory = this.resolver.resolveComponentFactory(
BackDropItemComponent
);
const componentRef = this.container.createComponent(factory);
componentRef.instance.imagePath = objectUrl;
}
}
});
}
The problem is that angular wraps the nested component within a div:
<div _nghost-qon-c42="" skick-back-drop-item="" class="ng-star-inserted">
So it is not rendered because of the special element tags created. If that div element is removed, it renders the nested SVG properly.
I know i can render the nested component as a directive and it would work, something like this:
<svg>
<svg:g skick-back-drop-item />
</svg>
but in my use case, the controller needs to be rendered dynamically from the parent component, so that approach does not work for me.
Maybe is there a way to render (add the nested controller) as a directive from the parent component pro-grammatically (bear in mind the nested component has @input properties) ?
or
Is it possible to have invisible component containers? Render the content of the nested component without any container?
Parent template:
<div class="patch-overlay" cdkDrag>
<svg xmlns="http://www.w3.org/2000/svg" version="1.1" xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:svgjs="http://svgjs.dev/svgjs" width="100%" height="100%">
<<ng-container #svgContainer></ng-container>>
</svg>
</div>
Nested Component Template (Example):
<svg width="400" height="110">
<rect [attr.width]="width" height="100" style="fill:rgb(0,0,255);stroke-width:3;stroke:rgb(0,0,0)" />
</svg>
Solution 1:[1]
Since this shows up as the most recent on the first page of google for the question here is the workaround that seems to work in all cases: using display contents.
@Component({
selector: 'app-my-component',
templateUrl: './my-component.component.html',
styleUrls: ['./my-component.component.sass'],
host: {
style: "display: contents"
}
})
export class MyComponent {}
This works by telling the browser to act as if the host element isnt there at all; it will still be part of the html if you do inspect element but it will not be part of any rendering/sizing/position considerations. P.S. this does have an advantage over not including it at all in the html, it is still a selectable html element for CSS
Solution 2:[2]
I've tried to do what you're doing. My mistakes include building specialized FormGroup components (don't do that). Recently, I built a component using ControlValueAccessor and it works really well and serves my purpose.
I haven't built anything with ng-template and its associated features, like ngTemplateOutlet. However, I suspect this might help you. ng-template gets moved out of the way in the DOM, which is what you're looking for.
Here's Angular Univeristy's explanation. I also find Netanel Basel has great info.
If you want to try the CVA route, last week I just got done with recoding on a project and did a bunch of research here on SO. Found a question that touched on what I wanted to do and went back and wrote a new answer that worked for me. Check it out, it might give you what you need.
Solution 3:[3]
Building on TurtleKwitty's great answer, their exact solution gave me the error Use @HostBinding or @HostListener rather than the host metadata property
I solved by moving it to my component's .scss file, like so:
:host {
display: contents;
}
Hope that helps someone else!
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | TurtleKwitty |
| Solution 2 | Andrew Philips |
| Solution 3 | Joel Balmer |
