angular / angular

Deliver web apps with confidence 🚀

Home Page:https://angular.dev

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Components host-element should (optional) be a html-comment instead of html-element

MickL opened this issue · comments

I'm submitting a...


[ ] Regression
[ ] Bug report
[X] Feature request
[ ] Documentation issue or request
[ ] Support request

Current behavior

Component-containers like <app-root>, <app-hero-list>, etc. do break flex layout.

Expected behavior

A component(or whole module) should have the option to create no html-element. Instead Angular should work with comments instead of html-elements, exactly like ng-container.

Minimal reproduction of the problem with instructions

<html style="width: 100%; height: 100%;">
   <body style="width: 100%; height: 100%; display: flex; flex-direction: column;">
      <app-root>
            <div style="flex: 1; background: lightcoral;">I should have full height!</div>
      </app-root>
   </body>
</html>

Plunker: https://plnkr.co/edit/aOWUciRx2EsRsKwL2zma?p=preview
(Uncomment app-root in styles.css to see currently needed fix)

What is the motivation / use case for changing the behavior?

Using display:flex only affects the child-element which gets broken by any <app-component> tag. To apply the example above with multiple nested components to require ugly, hardcoded stylesheets:

   body,
   app-root,
   app-hero,
   app-hero-list,
   app-hero-detail {
       display: flex;
       flex-direction: column;
  }

Also, as a frontend developer, i don't like the <app-component> tags either :D

Environment


Angular version: 4.x.x / 5.x.x

This is a totally different framework. I just want to not break the cascade. If i would create the app with plain html an js there would be no unnecessary containers, too.

@MickL ... <ng-container> and <ng-template> work with comments. Also attribute components like my-columns in <tr my-columns></tr>.

If I understand you suggest to Angular does the same for any place, any component, ... and so on.

It would be impossible to keep consistency between ViewEncapsulation.Native and ViewEncapsulation.Emulated, since comment cannot have shadow root.

At least :host would not work anymore. Styles could be generated into global css instead.

@mlc-mlapis Im not sure if you understand me right. I wish for replacing component-containers like <app-name>name works!</app-name> with <!--<app-name>-->name works!<!--</app-name>--> to create no dom-element for the component itself.

@MickL ... yes, I understand what you want, just wanted to be sure. You came probably from another world ... :host would not work, ... all is moving from global scope to an encapsulated form (Emulated or Native) and you go opposite? And also replacing component-containers ... when general custom components concept is under heavy development?

I do work with Angular a lot and like css encapsulation. It was just an idea to make non-visible-component-containers possible because they disturb. If shadow dom is not possible with comment-containers then screw it, comment-containers may be more important because css will not work correctly anyway.

The expected behavior is exactly the same as ng-container

With a solution to this, I believe you're right @mlc-mlapis - :host and Shadow DOM could probably not work. However, when you want something like this - I guess you don't care about Shadow DOM, and :host shouldn't really apply either. You want HTML "tightness" when, in fact, components produce html that belongs to the same "css system".

I guess the case that seems hard (impossible?) to accomplish with angular is

<table>
<tbody>
  <some-rows-component></some-rows-component> <!-- Produces a couple of <tr>s -->
  <some-other-rows-component></some-other-rows-component>
</tbody>
</table>

EDIT:
One way to do it is

class NoRootTagComponent implements OnInit {
    @ViewChild(TemplateRef) template;

    constructor(private vcRef:ViewContainerRef) {}

    ngOnInit() {
        this.vcRef.createEmbeddedView(this.template);
    }
}

@Component({
  template: `<ng-template>
<tr>...</tr>
<tr>...</tr>
</ng-template>`
})
class SomeRowsComponent extends NoRootTagComponent {
   constructor(vcRef:ViewContainerRef) {
        super(vcRef);
    }
}

@staeke ... and what about to use attribute components like:

<table>
<tbody>
  <tr some-rows-component></tr>
  <tr some-other-rows-component></tr>
</tbody>
</table>

Well @mlc-mlapis, you’d get trs within trs

@staeke ... ah, I thought about it from a wrong side. My mistake.

@staeke does not work as is for me because the OnInit does not capture the templateRef. However adding a hash-variable on ng-template + reading it by name works fine:

<ng-template #template-ref>asd</ng-template>
@ViewChild('template-ref') template;

From #22208:

Related:

Declaration suggestion:

Updated:

Per discussion in the original issue, and comments that @trotyl made there, I think the best way to do this is with the introduction of a new decorator. @Fragment. This new decorator will allow an object with a template reference, and some of the properties that currently have the @Component decorator. However, it would not have any styles, or styleUrls properties, neither would have a host property. Also, @HostBinding and @HostListener decorators should have not effect, as there would be not host to be bounded to.

A @Fragment could be used when a templateRef is needed, and should be assigned to a templateRef variable in a template assignment.

Original:

This could be just a new property in the @Component declaration. Like a boolean. Or another decorator just for this propuse.

Implementation suggestion:

Update:

A @Fragment should only allow template attributes that are explicit set as @Input in its definition. As it won't be a new component what so ever, additional attributes declarations should be disallowed. This could reduce the complexity of asking where this additional attributes should be placed, and at the same time, would reduce the usage of a @Fragment to what it really supposed to be used, a component to declare a reusable template.

Original:

Some of the concerns about this is, styles and classes names, and where to applied them. But currently with the Shadow DOM emulation a new attribute is added to the children of the component, so this could be mean that their styles will still being the same.

If the developer declare styles to the component using host pseudo selector, this would create a new Shadow DOM. And that styles should be applied as-is declared. Angular should not try to figure out what the developer would want.

If the developer apply styles or classes names a fragment tag this should be ignored, cause the element would disappear. This should be the expected behavior.

Fragment should only allow attributes that are declared as @Input properties, effectively ignoring and throwing any other valid attribute.

Proposal syntax (Original to this comment):

Input:

my-fragment.fragment.ts

@Fragment({
    selector: 'app-my-fragment',
    template: `
    <div>Hello {{ name }}, I'm inside a fragment</div>
    `,
})
class MyFragmentFragment {
    @Input()
    name: string;
}

app.component.ts

@Component({
    selector: 'app-root',
    template: `
        <app-my-fragment #myFragment [name]="myName"></app-my-fragment>
    `
  })
  export class AppComponent {
    @ViewChildren('myFragment')
    myFragment: MyFragmentFragment; // I think it should be also assignable to TemplateRef, maybe the type should be TemplateRef<MyFragmentFragment>
    myName = 'SuperCoolName';
  }

Output:

<!-- Omitting body and everything -->
<app-root>
  <!-- --> 
  <div>Hello SuperCoolName, I'm inside a fragment</div>
</app-root>

@michaeljota My original comments is about having a declarative EmbeddedView approach per #22208 (comment), but here is more close to using ComponentView on host-element-free condition. Technically the latter could be better, but my comments is not for that story.

I thought angularjs did the job just fine with replace=true. Don’t know why we can’t keep it as simple as that? I could also have multiple “ng-content” like declarations in angularjs. That ol gal angularjs still has features I miss these days

@AckerApple I don't know exactly the issue where is described, you can search for it an pin it here. First, you can replicate the behavior you would like using directives instead. Second, the replace=true option was actually created with unnecessary complexity, most of that complexity was related to the fact that you are allow to place additional attributes in all directives in AngularJS, so it would need a way to understand where apply those attributes in the created elements. As this Angular was not made only to support the web but also another kind of renderers, I've read that this is not coming back.

However, the concept of Fragments is really different from the approach that AngularJS use. Even when the usage would appear to be the same, it would be optimized and should not make the assumptions that it make in AngularJS.

@michaeljota Your syntax and html output looks perfect, but having no event-listener, no styles etc would make my feature request useless. The point is having a component with all its features but no component-host-element (which destroys flex layouts)

Right on, gittie up. I’ll quote another commenter on the matter “I’ve done very bad things because of these limitations”.....

I know it’s got to be done different than replace=true, I think the denotation should be on the content caller such as <tag *ngReplace>

Using a component with an attribute selector to attach to an existing element OR using a directive (as you are recommending @michaeljota) works MOST of the time but not all the time ESPECIALLY as @MickL points out when you still need to produce content output (Component versus Directive).

My end all point: The ol angularJs produced results in two ways that I’m wishing Angular would solve. Replacing tags and multiple ng-content outputs. The latter seems a closed case that it won’t happen because of the nature of how the ng-content “projects” content.

Thank you for the reply. I get my work done with or without these enhancements

@MickL, I only need the replace tag to stop breaking flex layouts too

Same boat as you

@MickL The thing is you have to attach that even listener to something, and the current something those are attached are the host components themselves, which we don't want to exist.

The complexity that AngularJS had to work with is just about that. If the element is not being creating, which element would be attached this attributes (aka, host binding and host listeners).

@AckerApple I know that normal directives won't work all the time. As per your comment *ngReplace would be a template directive. You can even use some of the workarounds out there to develop that idea.

Still, my proposal is not just about replacing the host element, or deleting it. It is about having a component that does not have a host in the first place.

BTW. Angular have Angular-Flex to apply flex to components. Those are, as you can imagine, directives.

Very insightful. A component with no host sounds very intriguing and better than my proposed ngReplace.

Onward and upward. You Angular engineers are the American astronauts of my world

Something like portals (https://material.angular.io/cdk/portal/overview) may be helpful in some cases.
https://stackblitz.com/edit/angular-dj6ayc

Drawback is that you must set styles programmatically on the projected elements or use the deprecated ::ng.deep selector.

BTW2: @MickL

@michaeljota Your syntax and html output looks perfect, but having no event-listener...

You won't have a host element in the fragment, and no way to attach anything to the host, but you still can have event emitter, that emits when your components inside the fragment listen to an event. So, I think most of the scenarios would be covered. What do you think?

Having no component styles actually makes it useless for me. Shouldnt they work, except for :host?

Everything I want to say is just my opinion. Don't take it offensive.

The issue feels quite... Well, not an issue. 9 upvotes for more than one year since the issue is open.

I use Angular in combination with flex since beta versions of Angular 2 across dozens of projects and faced this problem only two times; both times #18877 (comment) helped me out. It is nearly exactly repeating the thing you are talking about. It is ugly, but it works.

You are proposing to introduce a huge change to the framework. You want another type of directive to come into play; this needs to be documented, tested, supported, etc; also tooling must be adapted (compiler, angular cli etc). It does not feel native (from current architecture prospective); it introduces ambiguity in reading the template because you never know whether this is a template or fragment; it is just another layer of complexity.

I don't say idea is bad. I want to say there is no enough reasons to create such a big change. I would rather start with collecting the reasons instead of planning where the styles will be defined.

@smnbbrv . You mention you would like focus on the reasons for this request.... I have prepared a condensed example of my code that demonstrates my need for a solution.

It's a semi-complicated need I have. So please forgive me as in my next comment, I paste a bunch of code that I can't really express any other way at this time.

Here is comes in 30 seconds, 100 lines only (not too bad right)

I am willing to try and take a stab at representing my need for it.

My source code is too huge to link to nor make a plunker of. Please follow along as I try to keep it short.

I have the need to:

  • Display multiple form fields using one component
    • A data object defines the values
    • A Field defintion translates how to read the data
  • Provide an override for how the fields are wrapped

I have a slimmed down version of my need, posted below...

Desired Example Results:

  • My display-fields should be the ONLY Angular component tag left in html output

Produced Unwanted Example Results:

  • I can't find a way to omit the component display-field from being output to html

HINT: watch out as one component is display-fields and another is display-field

AppComponent

Creates data and field definitions to supply to my own display-fields component. This template wants its children to flex-grow via template override.

const template=`
<display-fields
  [fields] = "fields"
  [data]   = "data"
  style    = "display:flex"
>
  <ng-template #wrap="" let-outlet="outlet">
    <div style="flex-grow:1">
      <ng-template *ngTemplateOutlet="outlet"></ng-template>
    </div>
  </ng-template>
</display-fields>
`

@Component({
  template: template
}) export class AppComponent{
  data = {first_name:"Acker", last_name:"Apple"}
  fields = [{
    name:"first_name"
  },{
    name:"last_name"
  }]
}

DisplayFields

Loops fields and decides if an override wrap is defined

const displayFieldsTemplate=`
<ng-container
  *ngFor = "let field of fields"
>
  <ng-container
    *ngTemplateOutlet="wrap || defaultWrap;context:{outlet:defaultWrap}"
  ></ng-container>
  <ng-template #defaultWrap="">
    <display-field
      [data]  = "data"
      [field] = "field"
    ></display-field>
  </ng-template>
</ng-container>
`

@Component({
  selector: "display-fields",
  template: displayFieldsTemplate
}) export class DisplayFields{
  @Input() data:any
  @Input() fields:{name:string}[]

  @ContentChild("wrap") wrap:TemplateRef<ElementRef>

  /* I need NO display-field tags breaking the flex-grow */
}

DisplayField

Contains two tags. A label and an input. The width:100% on the input is not going to grow as desired

const displayFieldTemplate=`
  <label>{{ field.label }}</label>
  <input [(ngModel)]="data[field.name]" style="width:100%" />
  <!-- The above input will NOT grow it's width with the flex-grow as display-field blocks it -->
</ng-container>
`

@Component({
  selector: "display-field",
  template: displayFieldsTemplate
}) export class DisplayField{
  @Input() data:any
  @Input() field:{name:string}

  /* NO HOST IN HERE, PLEASE */
}

Recap

My input tags do NOT grow width because they are always wrapped in a display-field tag.... I can't make display-field an attribute selector component such as [display-field] because display-fields has NO html tags in it to attach on to

@MickL for reasons why is not technically possible right now to have styles in a host-less component, see this comment

@smnbbrv I understand your point, still I think for that kind of styles, this would be useful. For me, this is for other scenarios, for example, if you want to work with NativeScript and create reusable components with Angular and nothing else.

My proposal is really about to create another type of directive, yes. But the reason why the components won't be as they used to be in ng1 are already been exposed, and I really doubt that they will introduce some kind of remplace: true again.

I really want this as well, I agree that it's a significant deficiency in Angular. The workarounds result in verbose code and maintenance difficulties.

I'll summarize the reasons from above that people seem to be saying this capability is important, and add a new? one. (I'm not trying to put words in anyone's mouth, just trying to make the case for how important this is.)

  1. The host element can get in the way of CSS cascading and required parent/child relationships.
  2. The host element can obstruct required parent-child element relationships that are required by HTML (eg <table>, <ul/ol><li>), or at a minimum it restricts how components can be used in an HTML tree.
  3. (new?) The host element can create layout challenges that are non-trivial to fix (at a minimum, they have to be fixed in multiple places and require knowledge of the workaround). Eg the host element has a line height, so if the component wraps an image or svg it can add to the size.

A use-case for #3 is implementing an SVG icon component, eg selector: svg-icon - the containing host element adds a line-height since it's a text node, making layout (unnecessarily) more difficult. The approach of using an attribute selector (eg selector: svg[svg-icon]) isn't really viable, bc the Angular template parser objects to SVG elements in the template without a parent <svg> element. There is probably a reasonable way to make svg[svg-icon] work, but I haven't figured it out yet.

There are some use-cases it can benefit and also improves the HTML div structure.

It's not uncommon for frameworks, for example, Auralia has it as well:
Function: https://aurelia.io/docs/api/templating/function/containerless/
Example: https://discourse.aurelia.io/t/use-of-containerless/817

Using a directive would reduce the need for a containerless approach. But since it has the drawback that you can't use an (S)CSS file for styling (#17766) this doesn't work for more elaborate use-cases.

Elements like ng-container or ng-template do not create a visible html-element, too.

I created this issue one year ago, since then i encountered multiple projects where flex-layouts broke because of the component-container-elements. Fixing that always required an ugly workaround.

Upvoted. This feature would allow much easier creation of Angular components for various UI frameworks. With 3rd party UI frameworks a certain DOM structure is often required.

One can fix it by overriding styles manually .. which as you can imagine results in messy, fragile code

Any progress on this?

Upvoted as this feature is an absolute must have. For now I'll do a mechanic where I clone my node, remove the parent, and append the node to the grandparent on init, but that's serious duct tape.

Angular has been around for a while already and component containers should be optional. I'd even say that having a container should not be the default behavior, unless there is a class or a styling added to the component.

We don't just need 'host-less' components, we also need components where the template can add directives or styles easily to its standard host element without all the issues and complications of currently doing so.

  • Currently if you try to modify a @HostBinding value it can cause change detection issues. This is because the node you're modifying is really owned by the container component (in a different _ngHost css scope). It would be really nice not to have this constraint.
  • You could use async pipe safely, where today @HostBinding must be represented by a non-observable value.
  • As others have mentioned this is important for @angular/flex where I may want to apply directives on the host - such as fxLayout="column" without a wrapping element or other nasty hack.
  • This would mean the template for a component could conditionally add styles / classes / attributes to the host element. Without having to use @HostBinding. Would be great :-)

There would probably be a need for 'fragments' too as described by others, where the original component container isn't created - but personally I've more often had a desire to add something to the host rather than not have one.

I'd love to see a syntax like this in the template, kind of analogous to what :host does for styles. It would promote anything defined on it up to the host container - so there would probably have to be some limitations. I think I've seen this idea before, but not in this thread.

<ng-host fxLayout="column" [style.width]="responsiveService.width$ | async>
     ....
</ng-host>

I suppose these properties defined on ng-host would be equivalent to 'inputs' for change detection? Then it would then be safe to modify them and be able to correctly trigger change detection for the control without an error.

A componentless component / fragment could be useful for something like a facebook widget:

 <app-facebook-widget type="like" url="https://angular.io"></app-facebook-widget>

The template could be:

 <ng-container *ngIf="type == 'like'">
      <fb-like data-url="url"></fb-like>
 </ng-container>
 <ng-container *ngIf="type == 'share'">
      <fb-share data-url="url"></fb-like>
 </ng-container>

Then that would become something like this in the final HTML:

<fb-like data-url="https://angular.io"></fb-like>

You could just apply flex: 1 to your host:

:host {
  flex: 1;
}

Or:

@Component({ selector: 'app-my-component', styles: [ ':host { flex: 1 }' ] })
class MyComponent {}

That's it.

AppComponent

Creates data and field definitions to supply to my own display-fields component. This template wants its children to flex-grow via template override.

const template=`
<display-fields
  [fields] = "fields"
  [data]   = "data"
  style    = "display:flex"
>
  <ng-template #wrap="" let-outlet="outlet">
    <div style="flex-grow:1">
      <ng-template *ngTemplateOutlet="outlet"></ng-template>
    </div>
  </ng-template>
</display-fields>
`

@Component({
  template: template
}) export class AppComponent{
  data = {first_name:"Acker", last_name:"Apple"}
  fields = [{
    name:"first_name"
  },{
    name:"last_name"
  }]
}

DisplayFields

Loops fields and decides if an override wrap is defined

const displayFieldsTemplate=`
<ng-container
  *ngFor = "let field of fields"
>
  <ng-container
    *ngTemplateOutlet="wrap || defaultWrap;context:{outlet:defaultWrap}"
  ></ng-container>
  <ng-template #defaultWrap="">
    <display-field
      [data]  = "data"
      [field] = "field"
    ></display-field>
  </ng-template>
</ng-container>
`

@Component({
  selector: "display-fields",
  template: displayFieldsTemplate
}) export class DisplayFields{
  @Input() data:any
  @Input() fields:{name:string}[]

  @ContentChild("wrap") wrap:TemplateRef<ElementRef>

  /* I need NO display-field tags breaking the flex-grow */
}

DisplayField

Contains two tags. A label and an input. The width:100% on the input is not going to grow as desired

const displayFieldTemplate=`
  <label>{{ field.label }}</label>
  <input [(ngModel)]="data[field.name]" style="width:100%" />
  <!-- The above input will NOT grow it's width with the flex-grow as display-field blocks it -->
</ng-container>
`

@Component({
  selector: "display-field",
  template: displayFieldsTemplate
}) export class DisplayField{
  @Input() data:any
  @Input() field:{name:string}

  /* NO HOST IN HERE, PLEASE */
}

Recap

My input tags do NOT grow width because they are always wrapped in a display-field tag.... I can't make display-field an attribute selector component such as [display-field] because display-fields has NO html tags in it to attach on to

@AckerApple Hello, I've come up with a workaround to remove the host component: https://stackblitz.com/edit/angular-fragment-emulation-display-fields?file=src%2Fapp%2Fdisplay-field%2Fdisplay-field.component.ts

The basic idea is as follows:

  1. Create a host component exposing a fragment template
@Component({
  selector: 'host-component',
  template: `
<ng-template #fragment>
  <ng-container>
    <!-- start writing the actual fragment content here -->
  </ng-container>
</ng-template>
`,
})
export class HostComponent {
  @Input() input1;
  @Output() output1;
  @ViewChild('fragment', { static: true }) fragment!: TemplateRef<void>;
}
  1. Create a corresponding fragment directive to instantiate the template as well as forward inputs and outputs to the host component
@Directive({ selector: '[fragment]', exposeAs: 'fragment' })
export class Fragment {
  @Input('input-alias') input1;
  @Output() output1;
  // use a helper function to instantiate the component and set up outputs forwarding
  private hostComponentRef = createComponentRef(...);
  constructor(vcRef: ViewContainerRef) {
    vcRef.createEmbeddedView(this.hostComponentRef.instance.fragment);
  }
  ngOnChanges(changes: SimpleChanges) {
    // helper function to forward inputs
    forwardOnChanges(this.hostComponentRef, changes);
  }
  ngOnDestroy() {
    // helper function to clean up
    forwardOnDestroy(this.hostComponentRef);
  }
  getComponent() {
    return this.hostComponentRef.instance;
  }
}
  1. Use the fragment directive in other templates
<ng-container fragment #fm="fragment" [input-alias]="..." (output1)=""></ng-container>
<!-- use fm.getComponent() to access HostComponent -->
  1. If you need ViewChild in HostComponent, write a view-child directive to forward the query results.
@Component({
  selector: 'host-component',
  template: `
<ng-template #fragment>
  <ng-container [view-child]="this">
    <div *ngIf="..." #div>my fragment</div>
  </ng-container>
</ng-template>
`,
})
export class HostComponent {
  @ViewChild('fragment', { static: true }) fragment!: TemplateRef<void>;
  div: ElementRef<HTMLDivElement> | undefined = undefined;
}

@Directive({ selector: '[view-child]' })
export class ViewChildDirectiveForHostComponent {
  @Input('view-child') component!: HostComponent;
  // use corresponding ContentChild
  @ContentChild('div', { static: false }) div: ElementRef<HTMLDivElement> | undefined = undefined;
  ngAfterContentChecked() {
    this.component.div = this.div;
  }
}

Additional Notes:

  • Remember to add HostComponent to @NgModule.entryComponents
  • You might want to use an interface to force HostComponent and Fragment to implement the same inputs and outputs
  • Projection (<ng-content>) is not supported. You might want to use input + template instead.
    <ng-container fragment [content]="tmpl">
      <ng-template #tmpl>
        <!-- view to project -->
      </ng-template>
    </ng-container>
    
  • This is merely a prototype, without enough checks and cleanups

As cumbersome as the workaround may be, I still hope it helps.

Upvoted.
I need this feature eagerly. React support this feature with "React Fragments" and I currently fallback to React because of this.

Any news?!

It seems that here everything is shut down because of Ivy. Nobody from the Angular team is in this issue.

I too CONSTANTLY run into these problems... I feel like building higher order components, and composition of UI components over UI inheritance would be MUCH MUCH more viable with this functionality.

I too wish it could be as simple as having everything behave as it does today with a replace: true like the old days (requiring nothing special other than a flag on the component), but would be happy with the fragment syntax several people above pointed out.

#6710

Just one of many other seemingly related issues that has previously been closed.

Let me understand what you guys are trying to achieve.....

So, for example, I want to create a UI part for anchors. In React I would do something of this sort:

<MyAnchor link="#">Test</MyAnchor>

In DOM, this would render a semantic tag together with the computed/dynamic attributes:

<a class="anchor" href="#">Test</a>

Simply put, the <MyAnchor> tags do not appear in DOM. However, in Angular these would be rendered in DOM. Just a simple example:

<my-anchor>
  <a class="anchor" href="#">Test</a>
</my-anchor>

For single layer components, this perhaps wouldn't pose many problems. At most you would include role=presentation so you avoid the Generic Containers in the Accessibility Tree. However for multi layer components (such as lists, tables, flex, etc..) the tags would be a show stopper (for CSS, semantics, accessibility... everything!).

Since I always want my code to compile to valid HTML5 with good semantics and a proper accessibility tree, I had to come up with a solution to remove the host containers. In the end, my solution was quite neat.

I used attribute selectors, and also specified the attribute as a boolean input property.

For example:

anchor.component.ts:

import { Component, Input } from '@angular/core';

@Component(
  {
    selector: '[neatAnchor]',
    styleUrls: [
      './anchor.component.scss',
    ],
    templateUrl: './anchor.component.html',
  },
)
export class AnchorComponent {

  @Input()
  public neatAnchor: boolean = true;

}

anchor.component.html:

<ng-content></ng-content>

anchor.component.scss:

:host {
  cursor: pointer;
  display: inline;
  transition: filter 0.25s cubic-bezier(0.645, 0.045, 0.355, 1);

  &:focus,
  &:hover {
    filter: opacity(70%);
  }
}

Now, to use this component, all I have to do is invoke it as follows:

<a [neatAnchor] href="#">Test</a>

I can easily switch to a button while retaining the same anchor style:

<button [neatAnchor] ...>Test</button>

Since the attribute selector is also an input property, I am able to wrap the selector in square brackets. This has the benefit of the selector not being rendered in DOM. Therefore, they would show up in DOM something of this sort:

<a href="#" _nghost-cxxx>Test</a>

or

<button ... _nghost-cxxx>Test</button>

The _nghost-cxxx can also be removed if you have no ViewEncapsulation, however you lose CSS scope... I wish Angular would use data-host-cxxx and data-content-cxxx instead for CSS encapsulation (something which Vue does) so that the document is truly HTML5 valid... but I know I cannot have everything my way! :)

Hope this helps.

This is exactly what display: contents is meant to solve.
Unfortunately support is not great, so using it right now is risky.

Someday...

This is exactly what display: contents is meant to solve.
Unfortunately support is not great, so using it right now is risky.

Someday...

perhaps... but honestly really why do we have to force pollution of DOM. That's what I don't understand. Just because we have things we can use to hack or bandaid the functionality so many people seem to want... why cant we just have component fragments, or the ability to do a replace, or the ability to render a component as something without injecting its own tags and its own dom structure.

I realize the nature of web components is that a component is an element... and has a tag for that... but I feel like angular today is significantly harder to build components with api's that are natural and intuitive, than even angularjs was.

I made the mistake of porting some components from react to angular, and while there were things about react I did not like, there are lots of things there that would make a component authors life so much better.

For a workaround, this has been implemented in @angular-contrib/common#ContribNgNoHostModule, where any component can declare an ngNoHost host attribute to eliminate the host element while keep other functionality working:

https://stackblitz.com/edit/angular-ng9d6x?embed=1&file=src/app/hello.component.ts

this is my component

<ng-template>
  <ng-content></ng-content>
</ng-template>
@Component({
  selector: 'table-row-group',
  templateUrl: './table-row-group.component.html',
  styleUrls: ['./table-row-group.component.scss'],
})
export class TableRowGroupComponent implements OnInit {

  @ViewChild(TemplateRef, { read: TemplateRef, static: true })
  public template: TemplateRef<void>;

  constructor(
      private readonly renderer2: Renderer2,
      private readonly elementRef: ElementRef<HTMLElement>,
      private readonly viewContainerRef: ViewContainerRef) {

  }

  public ngOnInit() {
    const comment = this.renderer2.createComment('table-row-group');
    const parentNode = this.renderer2.parentNode(this.elementRef.nativeElement);
    this.viewContainerRef.createEmbeddedView(this.template);
    this.renderer2.insertBefore(parentNode, comment, this.elementRef.nativeElement);
    this.renderer2.removeChild(parentNode, this.elementRef.nativeElement);
  }

}

this component work well in my project
image

this is my component

<ng-template>
  <ng-content></ng-content>
</ng-template>
@Component({
  selector: 'table-row-group',
  templateUrl: './table-row-group.component.html',
  styleUrls: ['./table-row-group.component.scss'],
})
export class TableRowGroupComponent implements OnInit {

  @ViewChild(TemplateRef, { read: TemplateRef, static: true })
  public template: TemplateRef<void>;

  constructor(
      private readonly renderer2: Renderer2,
      private readonly elementRef: ElementRef<HTMLElement>,
      private readonly viewContainerRef: ViewContainerRef) {

  }

  public ngOnInit() {
    const comment = this.renderer2.createComment('table-row-group');
    const parentNode = this.renderer2.parentNode(this.elementRef.nativeElement);
    this.viewContainerRef.createEmbeddedView(this.template);
    this.renderer2.insertBefore(parentNode, comment, this.elementRef.nativeElement);
    this.renderer2.removeChild(parentNode, this.elementRef.nativeElement);
  }

}

this component work well in my project
image

Great job. Would be nice if angular used this logic for components with a no host option.

Any progress? Would love to have someone from the Angular team comment on this issue. As things stand, I will be switching to Vue 3.x if nothing happens.

The component tag makes it harder to develop fine-grained reusable UI components because it will add so many useless component tags and make the html structure verbose and harder to understand in browser. Adding option to remove the component's tag will really make Angular slimmer and more agile.

As for event listeners, we can just do not allow developers to add event listeners on the no-host component itself. Instead, developers can just add event listeners inside the template of the component.

I absolutely need it as well. I have a simple layout feature and would like to implement snap-scrolling.
It's ssssoooo hard with the cascade of ng-component node in html.

What's up Angular team ??

I personally added the class to the custom tag itself (<app-root class="approot"></app-root>), am I doing something wrong? please advice

@simeyla mentioned that the futuristic display: contents would fix it. It does (on Chrome today it does.)

+1, this is a show stopper. it makes angular very difficult to use with css frameworks that rely on flexbox to rely on components layout... at the minimum.

@trotyl , putting this here in case you see it, but there is an infinite call stack issue with @angular-contrib/common ngNoHost

trotyl/angular-contrib#233

@mhevery, sorry for mentioning you directly but Ivy was released and may be something was changed? Could you please provide your thoughts and hopefully plans regarding that issue?

not even exaggerating... this lack of control is going to be the thing that pushes me away from angular.... its constantly having to fight the framework, and having to put hacks in place to get around this.

Honestly even if we had the capability to say render this component as a div or something, it would be infinitely better.

3 years and still no progress ? I switched from react to angular and have loved it until I started having troubles with css for this reason, which makes me kinda want to go back at times

Btw @ronnyek if you want components as divs (which is what I'm currently doing on pretty much every component) you do :host { display: block } (I think they added a cli option for this in. 9.1.0 which is ok, but ain't the main problem here)

For components as table-rows or cells you do :host { display: table-cell | table-row }

For a workaround, this has been implemented in @angular-contrib/common#ContribNgNoHostModule, where any component can declare an ngNoHost host attribute to eliminate the host element while keep other functionality working:

https://stackblitz.com/edit/angular-ng9d6x?embed=1&file=src/app/hello.component.ts

this one looks is not working wuth angular 9, at least not for me

`ERROR in node_modules/@angular/platform-browser/animations/animations.d.ts:71:22 - error TS2720: Class 'ɵangular_packages_platform_browser_animations_animations_f' incorrectly implements class 'Renderer2'. Did you mean to extend 'Renderer2' and inherit its members as a subclass?
Property 'childNodes' is missing in type 'ɵangular_packages_platform_browser_animations_animations_f' but required in type 'Renderer2'.

71 export declare class ɵangular_packages_platform_browser_animations_animations_f implements Renderer2 {
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  node_modules/@angular-contrib/core/render-extension/render-extension.module.d.ts:5:9
    5         childNodes(node: Node): NodeList;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'childNodes' is declared here.
node_modules/@angular/platform-browser/animations/animations.d.ts:102:22 - error TS2720: Class 'ɵAnimationRenderer' incorrectly implements class 'Renderer2'. Did you mean to extend 'Renderer2' and inherit its members as a subclass?
  Property 'childNodes' is missing in type 'ɵAnimationRenderer' but required in type 'Renderer2'.

102 export declare class ɵAnimationRenderer extends ɵangular_packages_platform_browser_animations_animations_f implements Renderer2 {
                         ~~~~~~~~~~~~~~~~~~

  node_modules/@angular-contrib/core/render-extension/render-extension.module.d.ts:5:9
    5         childNodes(node: Node): NodeList;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'childNodes' is declared here.

95% emitting CopyPlugin
ERROR in node_modules/@angular/platform-browser/animations/animations.d.ts:71:22 - error TS2720: Class 'ɵangular_packages_platform_browser_animations_animations_f' incorrectly implements class 'Renderer2'. Did you mean to extend 'Renderer2' and inherit its members as a subclass?
Property 'childNodes' is missing in type 'ɵangular_packages_platform_browser_animations_animations_f' but required in type 'Renderer2'.

71 export declare class ɵangular_packages_platform_browser_animations_animations_f implements Renderer2 {
                        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  node_modules/@angular-contrib/core/render-extension/render-extension.module.d.ts:5:9
    5         childNodes(node: Node): NodeList;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'childNodes' is declared here.
node_modules/@angular/platform-browser/animations/animations.d.ts:102:22 - error TS2720: Class 'ɵAnimationRenderer' incorrectly implements class 'Renderer2'. Did you mean to extend 'Renderer2' and inherit its members as a subclass?
  Property 'childNodes' is missing in type 'ɵAnimationRenderer' but required in type 'Renderer2'.

102 export declare class ɵAnimationRenderer extends ɵangular_packages_platform_browser_animations_animations_f implements Renderer2 {
                         ~~~~~~~~~~~~~~~~~~

  node_modules/@angular-contrib/core/render-extension/render-extension.module.d.ts:5:9
    5         childNodes(node: Node): NodeList;
              ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
    'childNodes' is declared here.

I would not risk to use a contrib package since if angular core changed implementation, it could break easily. Angular team, please help on this ticket! This is the single most important feature that can help developer build slim find-grained reusable components.

I've had issues with ngNoHost, and the project is abandoned.

A more verbose, less magic approach:

import {Component, OnInit, TemplateRef, ViewContainerRef} from '@angular/core';

@Component({
  host: { style: 'display:none'  },
  selector: 'my',
  template: '<ng-template #content>Example</ng-template>'
})
export class MyComponent implements OnInit {
  constructor(private readonly viewContainer: ViewContainerRef) {}

  @ViewChild('content', { static: true }) content: TemplateRef<{}>;

  ngOnInit() {
    this.viewContainer.createEmbeddedView(this.content);
  }
}

This still inserts a host element, but it's removed from the view and the template content is its sibling.

<my style="display:none"></my>
Example

@pauldraper

image

import {
  Component,
  OnInit,
  ViewContainerRef,
  ViewChild,
  TemplateRef,
} from '@angular/core';

@Component({
  selector: 'bs-button',
  template: '<ng-template #content>Example</ng-template>',
  styleUrls: ['./bs-button.component.css'],
})
export class BsButtonComponent implements OnInit {
  constructor(private readonly viewContainer: ViewContainerRef) {}

  @ViewChild('content') content: TemplateRef<{}>;

  ngOnInit() {
    this.viewContainer.createEmbeddedView(this.content);
  }
}

May I ask you to share a working sample? Thanks.

In order to create the embedded view in the ngOnInit hook, the ViewChild must be marked static. (Otherwise, use a different hook.)

I've updated the previous code to include this change. Working example:

https://stackblitz.com/edit/angular-eumnmt

@pauldraper Thanks,

Is there a specific limit to this method?
How about performance?
Do you recommend relying on it to produce a component? However, Does not seem to be a better way.

@HamedFathi this is reliable and performant. It uses the same APIs as structure directives like ngIf.

The angular-contrib ngNoHost solution is more concise and perhaps offers some slight performance (memory) improvements by avoiding an element altogether, but it is more fragile.

@pauldraper Your solution is nice thanks but it still triggers me that it pollutes the dom with a useless tag with a display: none haha.

I really look forward for the angular team to implement this feature. In the mean time I'll use your workaround.

I am in need of this to position a FAB in an absolute position to its container and Firefox doesn't like it ....

this is my component

<ng-template>
  <ng-content></ng-content>
</ng-template>
@Component({
  selector: 'table-row-group',
  templateUrl: './table-row-group.component.html',
  styleUrls: ['./table-row-group.component.scss'],
})
export class TableRowGroupComponent implements OnInit {

  @ViewChild(TemplateRef, { read: TemplateRef, static: true })
  public template: TemplateRef<void>;

  constructor(
      private readonly renderer2: Renderer2,
      private readonly elementRef: ElementRef<HTMLElement>,
      private readonly viewContainerRef: ViewContainerRef) {

  }

  public ngOnInit() {
    const comment = this.renderer2.createComment('table-row-group');
    const parentNode = this.renderer2.parentNode(this.elementRef.nativeElement);
    this.viewContainerRef.createEmbeddedView(this.template);
    this.renderer2.insertBefore(parentNode, comment, this.elementRef.nativeElement);
    this.renderer2.removeChild(parentNode, this.elementRef.nativeElement);
  }

}

this component work well in my project
image

Great job. Would be nice if angular used this logic for components with a no host option.

@davidbusuttil does this work for you on Angular 10? I used it perfectly in A8, but tried to update now and...
For me it breaks with

image

(parentNode is null, and createEmbeddedView crashes)

@mhevery, sorry for mentioning you directly but Ivy was released and may be something was changed? Could you please provide your thoughts and hopefully plans regarding that issue?

Yes we are aware of this issue and it is something which we would like to fix. Unfortunately there are higher priority items on the list which is preventing us from getting to this one. So it will be a while before we get here. But I do agree with you that hostless components is something which Angular would benefit from.

@mhevery thanks for the reply from Angular team (unfortunately rare for some high voted issues). It's nice to understand whether this issue (13th upvoted out of 2700+) is seen as planned, undesirable, desirable but low priority, etc.

@mhevery I hope Angular team can implement it very soon. I just came across the needs for getting this implemented. See the snippet below for Ionic application. I'm trying to move the code between 'new component' comments to a new component, but the layout messed up. Because Ionic expects 'ion-label' is immediate child of 'ion-item', the component tag breaks the assumption.

<ion-list>
<ion-item>
 <!-- new component start -->
  <ion-label>
  </ion-label>
 <!-- new component end -->
</ion-item>
</ion-list>

@pauldraper Your solution is nice thanks but it still triggers me that it pollutes the dom with a useless tag with a display: none haha.

And C++ pollutes the symbol list with nonsense as well. Fortunately, looking at the output is the exception and not the rule.

IMO the biggest problem with #18877 (comment) is its length (half a dozen lines or so).

@mhevery I hope Angular team can implement it very soon. I just came across the needs for getting this implemented. See the snippet below for Ionic application. I'm trying to move the code between 'new component' comments to a new component, but the layout messed up. Because Ionic expects 'ion-label' is immediate child of 'ion-item', the component tag breaks the assumption.

<ion-list>
<ion-item>
 <!-- new component start -->
  <ion-label>
  </ion-label>
 <!-- new component end -->
</ion-item>
</ion-list>

same problem with ionic...

Here's my approach to remove top tag:

    const nestedElements = this.elementRef.nativeElement.childNodes;
    this.renderer2.removeChild(parentNode, this.elementRef.nativeElement, true);
    while (nestedElements.length > 0) {
      this.renderer2.appendChild(parentNode, nestedElements[0]);
    }

I experimented with this a couple of weeks ago and solved it with structural directives and lazy components without dom manupulation. Should work with angular components, multiple content-projection included, but I didn't have time to solve Ionic's slot content projection :( Maybe somebody knows how to fix it quickly. This is not a solution, just an idea.

You just put this in your template:

<ng-template [hcExtendedIonHeader] [translucent]="true">
  <ng-template [hcExtendedIonToolbar] [color]="'primatry'">
    <ion-buttons>...</ion-buttons>
    <ion-title>...</ion-title>
  </ng-template>
<ng-template>

Directive should import type for lazy component and dynamically replace ng-template with component:

// use import type here instead of import
// https://www.typescriptlang.org/docs/handbook/release-notes/typescript-3-8.html#type-only-imports-and-export
import type { IonHeader as LazyComponent } from "@ionic/angular";

@Directive({ selector: "[hcExtendedIonHeader]" })
export class ExtendedIonHeaderDirective extends HostlessComponentDirective<LazyComponent> {
  @Input()
  public hcExtendedIonHeader: unknown;

  @Input()
  public translucent: boolean;

  constructor(
    private readonly componentFactoryResolver: ComponentFactoryResolver,
    injector: Injector,
  ) {
    super(injector);
  }

  public async getComponentFactory(): Promise<ComponentFactory<LazyComponent>> {
    const { IonHeader } = await import("@ionic/angular");

    return this.componentFactoryResolver.resolveComponentFactory(IonHeader);
  }
}

Full code for HostlessComponentDirective is here

So in this directive you can extend component behavior or if you want to extend component's template, you can create wrapper-component with extended template, then extract this template in this directive to paste it instead.

While agree many of these workarounds will work, I just hoped it'd be something that would be a supported feature or stuff built into angular. I would use this a fair bit, and wouldn't want to have to go digging back through this issue to remember what I had to do to get them to work properly.

Is this future plan related to this issue?

image

@HamedFathi , no that's the 2nd most upvoted issue #8785.

Not sure how supported display: contents is but it resolves the issue for me. Could be a good temporary fix until the functionality is added into angular.

<app-my-component style="display: contents"></app-my-component>

@jonespb20 for the best answer in three years since issue created

Support can be found here:
https://caniuse.com/?search=Display%20contents

@jonespb20 OMG! If I only had known this sooner! thanks 👍

@Sina7312 Haha! You should have read the whole thread where I mentioned this in September 2019 ;-)

It's probably fairly safe to use now, but watch out for accessibility problems. I'm still really hoping for proper support for something better. Have beem doing some complex component factory stuff and not having this is a nightmare.

The functionality we need effectively already exists with ng-container, just currently lacking the ability to assign a template and directive to it. You can even put *ngIf on an ng-container so clearly Angular knows how to track a block of DOM that isn't isolated inside parent tags.

Something like this should surely be possible?

@Component({  selector: 'ng-container[stealthy-component]'   })

Then you use it in your template (or use a component factory):

<ng-container stealthy-component></ng-container>

The component code for stealthy-component would work like any other component, except you couldn't use @HostBinding or :host within your css. You should still be able to scope css to child components though (with encalpsulation) and you'd achieve the goal of a component-less template.

@mhevery surely this wouldn't be harder to implement than #8785 which is on the roadmap? Aren't all the pieces to make it work using something like my sample syntax already there? Or is this just a case where we're all completely naïve to the complexity!

@simeyla yes that is exactly this request. (<ng-container> is an HTML comment in the DOM.)

Good suggestion. Besides breaking flex layout, it also breaks CSS sibling selector (~). For example in Bootstrap the .invalid-feedback class depends on the .is-invalid class being applied to the sibling, which people will run into as an issue when they extract validation error listing into a nested component (obviously it's easy to circumvent, but still).

I have this problem too. Trying to apply Bootstrap classes to my compositions but they don't work as expected because of all the host component tags, and I'm not doing anything special, just try to implement a simple list group and see how that goes.. This is slowing down my progress a lot. I came from React and was pretty excited learning Angular for a couple of months with simple projects, but now that I started using it for something serious, I'm finding this issue to be a big PITA.

Editing to add that this issue is forcing me to add classes to the component tags in some cases, which defies the whole point behind encapsulation. I shouldn't have to remember that list-item should have col-sm-8 added to it, otherwise it won't work when within a list-container item who also has class .row in it. If I encapsulate these classes inside the inner html of each component, they won't work as expected because of the host tags between them.

I have this problem too. Trying to apply Bootstrap classes to my ...

(stepping out of the main subject but a must...)
@ChakirMrabet Hmmm, Bootstrap is depends on direct children? are you sure? are you placing the "li" in the component code?

Just adding another use case for components for svg fragments. AFAIK, svg does not allow custom web elements as children.

I can achieve everything I want using an attribute binding, but it requires me to push the base element into the parent component and use HostBindings, HostListeners, and :host css selectors.

e.g. a simple rect with some text:

<!-- parent component -->
<!-- should not have know that my app-svg-rect uses a g base element -->
<!-- need host magic to set style and click handler for g element -->
<svg>
  <g app-svg-rect *ngFor='let rect of rects' [rect]="rect"></g>
</svg>

<!-- svg rect component -->
@Component({
  selector: '[app-svg-rect]',
  template: `
    <svg:rect [attr.x]="rect.x" [attr.y]="rect.y" width="80" height="30"/>
    <svg:text [attr.x]="rect.x + 40" [attr.y]="rect.y + 15">{{rect.text}}</svg:text>
  `,
  styles: ...lots of references to :host...,
})

It would be much cleaner to write this as a normal component which does not render the base tag:

<!-- parent component -->
<!-- no idea of implementation -->
<svg>
  <app-svg-rect *ngFor='let rect of rects' [rect]="rect"></app-svg-rect>
</svg>

<!-- svg rect component -->
<!-- component defines root element -->
<!-- no host listeners / bindings / css magic required -->
@Component({
  selector: 'app-svg-rect',
  template: `
    <svg:g [ngClass]="rect.cssClass" (click)="onClick()"> 
      <svg:rect [attr.x]="rect.x" [attr.y]="rect.y" width="80" height="30"/>
      <svg:text [attr.x]="rect.x + 40" [attr.y]="rect.y + 15">{{rect.text}}</svg:text>
    </svg:g>
  `,
  styles: ...no :host required...,
})

@mhevery
Any chance for Angular 13!

Fact this is still outstanding all these years later is exactly the sort of reason I've shifted a fair amount of my dev to other frameworks. Yes you can achieve the same results any number of ways, but still hanging out waiting on official support. I simply have to fight the framework too much...

Working solution here: https://stackoverflow.com/a/35837189/1425426

Our code, based on this stack overflow answer, looks slightly different, but works from Angular 8-11 without any noticeable issues:

import { Directive, ElementRef, OnInit } from '@angular/core';
@Directive({
    selector: '[removeHost]',
})
export class RemoveHostDirective implements OnInit {

    constructor(private el: ElementRef) { }

    /**
     * wait for the component to render completely
     */
    ngOnInit(): void {
        // move all children out of the element
        const nativeElement: HTMLElement = this.el.nativeElement;
        const parentElement: HTMLElement = nativeElement.parentElement;

        // Check if a parent element exists
        // Prevents JS Errors:
        //   1. "Cannot read property 'insertBefore' of null"
        //   2. "Cannot read property 'removeChild' of null"
        if (parentElement) {
            while (nativeElement.firstChild) {
                parentElement.insertBefore(nativeElement.firstChild, nativeElement);
            }
            // remove the empty element(the host)
            parentElement.removeChild(nativeElement);
        }

    }

}

@doptrois

I used your directive in a simple Bootstrap Row component (in design-time)

 <div class="row" removeHost>
    <ng-content></ng-content>
  </div>

Usage

<bs-row>
  Hello
</bs-row>

But the result is odd!

image

The original is

image

but as a user, if I use the removeHost with component everything is OK.

<bs-row removeHost>
  Hello
</bs-row>

image

As a component developer, how can I use it inside my components in design-time?

@HamedFathi that solution is to remove element introduced in dom because of component like app-task or bs-row
If you want to remove html elements from component template then just don't add those tags

just use

<ng-content></ng-content>

instead of

 <div class="row" removeHost>
    <ng-content></ng-content>
  </div>

Looks like you wnat add class row to correct element , most probabaly to bs-row itself . You can achive that either using host property doc or Host bindings doc

@abdulkareemnalband

I am looking for b-container, b-row and b-col in BootstrapVue.

image

So I am looking for a bs-row without a host/wrapper around it (just a simple div).

Check this approach #18877 (comment) for what I am looking for.

@HamedFathi
In current angular implementation each component requires it own tag,
The best solution for bootstrap row/ column would be adding row/column classes to host element (aka b-row/b-column) itself either using host property or hostbindings

example

@Component({
selector:'b-row',
host:{class:'row'},
template:'<ng-content></ng-content>'
})
class BootstrapRow{}

@abdulkareemnalband

Thanks for the clarification, IMO your approach is OK when we are working on a div element (input-group and input-group-prepend)

<div class="input-group mb-3">
  <div class="input-group-prepend">
    <span class="input-group-text" id="basic-addon1">@</span>
  </div>
  <input type="text" class="form-control" placeholder="Username" aria-label="Username" aria-describedby="basic-addon1">
</div>

What if someone wants to create this part (span) as a component?

<span class="input-group-text" id="basic-addon1">@</span>

@mlc-mlapis

If we are creating a new bunch of components then we can shape it as we like but when you are writing a wrapper on top of some libraries like Bootstrap and ... there is no option. exactly where this feature is important.

@HamedFathi Sure, I know. There are different requirements for different cases. The article above is just one view of that theme.

@HamedFathi I can't read the article you referenced since I've hit my monthly limit(!), but...

Here is a full running sample for making a span into a component. You can make anything that can be reference by a selector into a component. The span IS the component using this method. View the source and you'll see only a span tag (no span-wrapper).

https://stackblitz.com/edit/span-to-component?file=src/app/app.component.ts

It could be tricky if the schema of what you're wrapping is complex, but it should mostly be possible to wrap an existing library like this, and you'd (have to / be able to) use their original structure.

However sometimes you really need a logical container without a DOM element which is where this issue really comes in handy.