59

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?

5 Answers 5

45

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

Sign up to request clarification or add additional context in comments.

4 Comments

This works well wherever you need a valid element, I've just used this for a small component for table headers <th> where the same problem occurs. One query though, is there any documentation you can link to for this?
@David - You won't find it in the manuals (yet), but to assure you it is blessed: Here is the thread where users make the case that sometimes (svg and tables) you need to use attribute selectors for modifying structure: github.com/angular/angular/issues/11280 . In response, the functionality was added (well, restored, actually) here: github.com/angular/angular/pull/11808 .
but what if I want to add multiple <rect> elements inside one <g> element (manually, not by *ngFor)
How do you add properties to an attribute selector?
39

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.

5 Comments

Could be performance issues is it what I thought at first. But currently using this way in my material design light components to replace host element otherwise having issues with displaying html
@Kuncevich Good to know that my answer helped, performance bottlenecks would be created when the element ChangeDetection is triggered as shadow DOM !== actual DOM. But if there are not many elements then this would not be that big. Hopefully ;)
We need some analogy of Angular1 replace:true stackoverflow.com/questions/22497706/… someone said that this feature is actually is in angular2 but unofficially supported.
@Kuncevic it's the other way around. It's still in Angular1 but deprecated there. This is why they don't want to add it in Angular2 in the first place. It turned out to cause to many issues to be worth the effort.
If you think of using this technique with a ngFor that is changing the data, you will get surprised how the UI will not update properly.
35

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.

5 Comments

Such a short, simple, and effective answer
This works well with no extra workarounds. Hopefully Angular will add the option to not render the host element at some point.
This didn't work for me. Maybe they remove this feature in one of the newer versions of Angular.
This doesn't necessarily work if there are selectors expecting a direct descendant.
Although parent element it is not removed, at least doesn´t modify visually your layout.
9

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.

2 Comments

"Does the template have its own separate variable scope, what variables can the template see?" Check out this one (in Template Content part): blog.angular-university.io/…
This IS the way to go if you are about to use *ngFor around this data. My scenario was to group <td> together. This code works perfectly. Make sure to set display: none on the tl-no-tag so it doesn't interfere with your colspan.
5

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

1 Comment

Thanks a lot for pointer this out. I was convinced that removing the host element was possible in Angular, but it was apparently something we used back in the days of AngularJS:

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.