SKOLZ
SKOLZ

Reputation: 209

How to remove extra wrapping elements in the rendered HTML?

I started learning angular 5 3 days ago so I'm quite new at it. I also use angularJS and React to develop applications and I think I don't understand how angular 5 components fully work. If I create for example a custom button that has a custom text inside (I'm not saying this should be done this way but it's a simple example that shows my point) like this:

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

The rendered DOM results in:

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

which is unreadable, I wanted to know if there's a way to remove this wrapping elements and just place the components layout replacing the tags resulting in the following structure:

<button>
  <span>
    My Text
  </span>
</button>

If there's no way of removing them what are your suggestions? thanks!

Upvotes: 12

Views: 11548

Answers (2)

Vega
Vega

Reputation: 28708

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

Upvotes: 10

ndvo
ndvo

Reputation: 989

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>

Upvotes: 3

Related Questions