'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