10

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!

Vega
  • 27,856
  • 27
  • 95
  • 103
SKOLZ
  • 169
  • 1
  • 1
  • 12

2 Answers2

9

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

rofrol
  • 14,438
  • 7
  • 79
  • 77
Vega
  • 27,856
  • 27
  • 95
  • 103
  • Thank youu! the problem with that is that i'll still have an unnecesary `span` wrapping my button. By looking at your code I thought I could write ` – SKOLZ Mar 22 '18 at 14:42
  • Well, I checked it. Now lets say I have 2 types of button `.button-1` which has a blue background and `.button-2` which has a red background. What I want to do is to add a `styles.css` to the button component with the 2 options. and in the app.layout do: ``. the problem I see there is that the ".button-1" styles will have to be placed in the `app.component.css` and can't be placed in the `button.component.css` so I'll have to declare those styles in every view that uses a button – SKOLZ Mar 23 '18 at 17:55
  • since the styles are component scoped. By the way what's the `ng-container` you added there? is it a dummy component that is removed when adding the template? – SKOLZ Mar 23 '18 at 17:56
  • I think that's a valid solution to my problem. I'll add a complete response soon! thank you very much for your help! – SKOLZ Apr 18 '18 at 03:41
  • One issue I have with this approach is the breaking of encapsulation. Are you aware of a way to keep it, and still define all the styles in the child component? Apparently https://github.com/angular/angular/issues/18877 is open on this topic, but seems stuck – Sébastien Tromp Aug 14 '19 at 07:20
  • @SébastienTromp, yes, checkout https://stackblitz.com/edit/angular-wa9uir?file=styles.css – Vega Aug 15 '19 at 02:43
  • @Vega In the example, the class is defined at the "app" level, right? I was wondering if the same thing could be achieved by defining it at the "button" level so that the parent stays unaware of the styling of the child (unless it wants to restyle it, of course) – Sébastien Tromp Aug 16 '19 at 16:23
  • @SébastienTromp It is styled from styles.css. But it could be done with host-context too: https://stackblitz.com/edit/angular-wujdwm – Vega Aug 16 '19 at 16:56
  • The problem with this solution and others is that it requires an understanding of Angular's internal workings. Something like this should be trivial to do in plain old JS and other languages. You have to wonder what's the aim of Angular. Is it productivity? Is it simplification? Is it enhancement? Angular appears to be an emperor wearing no clothes. – ATL_DEV Nov 09 '19 at 12:30
2

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>
ndvo
  • 939
  • 11
  • 16