How to remove extra wrapping elements in the rendered HTML?

Angular components are directives with templates. According to this:

Directive configuration @Directive({ property1: value1, ... })

selector: '.cool-button:not(a)' Specifies a CSS selector that identifies this directive within a template. Supported selectors include element, [attribute], .class, and :not().

So component selectors can be also attribute selectors. For your example, instead of writing this:

parent.component.html:

<app-button>
  <app-text>
    My Text
  </app-text>
</app-button>

write this:

parent.component.html:

<button app-button>
    <span app-text>My Text</span>
</button>

where :

app-button.component.ts

...  
  selector: '[app-button]',
  template: `<ng-content></ng-content>
...

app-text.component.ts

...
  selector: '[app-text]',
  template: `<ng-content></ng-content>`
...

this would be rendered as you expected:

enter image description here

Update after your comment about styling those buttons:

To style the buttons from inside the button component, and set class in parent component, use :host-context pseudo-class. It is not deprecated and works well

button.component.css

  :host-context(.button-1)  {
    background: red;
  }
  :host-context(.button-2)  {
      background: blue;
  }

app.component.html

<button app-button class="button-1">
    <span app-text>My Text</span>
</button>

<button app-button class="button-2">
    <span app-text>My Text</span>
</button>

Here is the DEMO


I had a similar issue. I'll provide my solution in case someone else has the same problem.

My component should be able to be used either within other components or as a route from <router-outlet></router-outlet>. When I used the selector as an attribute [my-component] things worked perfectly provided it was used within other components. But when created by <router-outlet></router-outlet> a <div> were created automatically.

To avoid that, we can simply use multiple selectors, and consider that the selectors can be combined.

Consider this: I want my component to use the attribute my-component and if it ever should be created by the <router-outlet></router-outlet> it should be wrapped in a <section></section>. To achieve this simply use:

@Component(
    selector: 'section[my-component], my-component',
    ...
)

The result will be, if used inside another tag:

<whatevertag my-component>
     ... component content ...
</whatertag>

If used as a route:

<section my-component>
     ... component content ...
</section>