Remove the host HTML element selectors created by angular component

SvgAngular

Svg Problem Overview


In angular 2, svg-rect is a component which creates rect like below,

<svg height="550" width="450" x="0" y="0">
    <g id="svgGroup">
        <svg-rect>
        <!--template bindings={}-->
            <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
        </svg-rect>
        <svg-rect>
        <!--template bindings={}-->
            <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
        </svg-rect>
    </g>
</svg>

but this won't render rect because of the special element tags created. If svg-rect tags are removed it renders the rect

<svg height="550" width="450" x="0" y="0">
    <g id="svgGroup">
        <!--template bindings={}-->
        <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
        <!--template bindings={}-->
        <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
    </g>
</svg>

In Angular 1.x, there is replace: 'true' which removes the directive tags with the compiled output. Can we implement the same in angular2?

Svg Solutions


Solution 1 - Svg

Instead of trying to get rid of the host element, turn it into one that is valid SVG but other wise unaffecting: Instead of your element selector

selector: "svg-rect"

and its corresponding element in the template:

template: `...<svg-rect>...</svg-rect>...`

switch to an attribute selector:

selector: "[svg-rect]"

and add that attribute to a group element tag:

template: `...<g svg-rect>...</g>...`

This will expand to:

<svg height="550" width="450" x="0" y="0">
    <g id="svgGroup">
        <g svg-rect>
        <!--template bindings={}-->
            <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
        </g>
        <g svg-rect>
        <!--template bindings={}-->
            <rect x="10" y="10" height="100" width="100" fill="red" stroke="#000" stroke-width="2"></rect>
        <!--template bindings={}-->
        </g>
    </g>
</svg>

which is valid SVG, which will render. Plnkr

Solution 2 - Svg

Another approach to remove the host of the component which I use.

Directive remove-host

//remove the host of avatar to be rendered as svg
@Directive({
	selector: '[remove-host]'
})
class RemoveHost {
	constructor(private el: ElementRef) {
	}

	//wait for the component to render completely
	ngOnInit() {
		var nativeElement: HTMLElement = this.el.nativeElement,
			parentElement: HTMLElement = nativeElement.parentElement;
		// move all children out of the element
		while (nativeElement.firstChild) {
			parentElement.insertBefore(nativeElement.firstChild, nativeElement);
		}
		// remove the empty element(the host)
		parentElement.removeChild(nativeElement);
	}
}

Using this directive;
<avatar [name]="hero.name" remove-host></avatar>

In remove-host directive, all the children of the nativeElement are inserted before the host and then the host element is removed.

Sample Example Gist
Based on the use case, there might be a few performance issue.

Solution 3 - Svg

What about something like:

:host { display: contents; }

Putting that in the host component's css (or scss) file will cause the component's box not to render.

N.B: It has worked for me before -- but obviously I'd be worried about browser compatibility especially if you're developing to support older browsers. I'm also pretty sure that's not entirely out of the "experimental" phase. The docs also state that this may cause accessibility issues.

Solution 4 - Svg

There's another approach that we can get the template of a component out of the component.
First, we create the component, whose tag we hope to remove from browser render (we are not trying to remove the tag here.)

@Component({
  selector: 'tl-no-tag',
  template: `
    <template #tmp>
      <p>{{value}}</p>
    </template>`,
  styleUrls: []
})
export class TlNoTagComponent {
  @ViewChild('tmp') tmp: any;
  value = 5;
}

Then, in another component's template, we write:

<tl-no-tag #source></tl-no-tag> <!-- This line can be placed anywhere -->
<template [ngTemplateOutlet]="source.tmp"></template> <!-- This line will be placed where needed -->

Then, we have in browser something like this:

<tl-no-tag></tl-no-tag>
<p>5</p>

So, we have brought <p>{{value}}</p> out of the TlNoTagComponent. <tl-no-tag></tl-no-tag> will continue to be there, but will not block any css or svg-thing.

Solution 5 - Svg

To quote the Angular 1 to Angular 2 Upgrade Strategy doc:

> Directives that replace their host element (replace: true directives in Angular 1) are not supported in Angular 2. In many cases these directives can be upgraded over to regular component directives. > >There are cases when regular component directives are not going to work, in those cases alternative approaches can be used. For example for svg see: https://github.com/mhevery/ng2-svg

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionBhavikView Question on Stackoverflow
Solution 1 - SvgJohn CView Answer on Stackoverflow
Solution 2 - SvgBhavikView Answer on Stackoverflow
Solution 3 - SvgtlmView Answer on Stackoverflow
Solution 4 - SvgTimathonView Answer on Stackoverflow
Solution 5 - SvgMark RajcokView Answer on Stackoverflow