279

I dont't know how to add to my component <component></component> a dynamic class attribute but inside the template html (component.html).

The only solution I found is to modify the item via "ElementRef" native element. That solution seems a little complicated to do something that should be very simple.

Another problem is that CSS has to be defined outside component scope, breaking component encapsulation.

Is there a simpler solution? Something like <root [class]="..."> .... </ root> inside the template.

lascarayf
  • 3,423
  • 3
  • 19
  • 24

11 Answers11

440

This way you don't need to add the CSS outside of the component:

@Component({
   selector: 'body',
   template: 'app-element',
   // prefer decorators (see below)
   // host:     {'[class.someClass]':'someField'}
})
export class App implements OnInit {
  constructor(private cdRef:ChangeDetectorRef) {}
  
  someField: boolean = false;
  // alternatively also the host parameter in the @Component()` decorator can be used
  @HostBinding('class.someClass') someField: boolean = false;

  ngOnInit() {
    this.someField = true; // set class `someClass` on `<body>`
    //this.cdRef.detectChanges(); 
  }
}

Plunker example

This CSS is defined inside the component and the selector is only applied if the class someClass is set on the host element (from outside):

:host(.someClass) {
  background-color: red;
}
Janos Vinceller
  • 1,208
  • 11
  • 25
Günter Zöchbauer
  • 623,577
  • 216
  • 2,003
  • 1,567
  • I had to do the `someField = true` in `ngOnInit()`-method instead of `ngAfterViewInit()`. I could not get it to work otherwise. – John Nov 25 '16 at 08:23
  • [Made a fork here](https://plnkr.co/edit/UsHyi78eza01N25ulxzA?p=preview) which shows the actual `:host` part working. Where can I learn more about host parameter in @Component() decorator (the syntax is not obvious to me, and the [@Component documentation doesn't explain very much](https://angular.io/docs/ts/latest/api/core/index/Component-decorator.html)) or learn more about your preferred HostBinding (it's only [listed as an Interface](https://angular.io/docs/ts/latest/api/core/index/HostBinding-interface.html) on Angular2 site?) – Nate Anderson Mar 03 '17 at 18:51
  • I don't know better docs, but it's just a different way of doing what you can do with `@Input()` `@Output()` `@HostBinding()` `@HostListener()` `@ViewChild(ren)()` `@ContentChild(ren)()` – Günter Zöchbauer Mar 03 '17 at 19:14
  • @GünterZöchbauer How would you go about using this, but for the inverse of the boolean value. In other words, how can you set the class on the host when the boolean value is false instead of true? – FreeAsInBeer Mar 10 '17 at 16:48
  • 1
    use a getter with a different name for the host binding that returns the inverted value `@HostBinding('class.xxx') get xxxclass(){ return !this.someField;}` – Günter Zöchbauer Mar 10 '17 at 16:55
  • is it possible to do something like that for directives and their events emitted ? – Ced Sep 09 '17 at 13:02
  • No, directives can only be added statically to a components template. – Günter Zöchbauer Sep 09 '17 at 15:27
  • @GünterZöchbauer - Why not adding wrapper inside the html and using simple class attribute? – Ponpon32 Oct 13 '19 at 08:42
  • @YochaiAkoka sure, you can, but not everybody wants that in every situation. – Günter Zöchbauer Oct 13 '19 at 08:47
  • @GünterZöchbauer - Thanks, just read it's also a bad practice to ignore the host element :) – Ponpon32 Oct 13 '19 at 08:52
  • 1
    @YochaiAkoka not sure what you refer to. I'm not aware of this rule. Less is usually more, so if you can avoid adding additional elements, then you should avoid it. – Günter Zöchbauer Oct 13 '19 at 08:57
280

Günter's answer is great (question is asking for dynamic class attribute) but I thought I would add just for completeness...

If you're looking for a quick and clean way to add one or more static classes to the host element of your component (i.e., for theme-styling purposes) you can just do:

@Component({
   selector: 'my-component',
   template: 'app-element',
   host: {'class': 'someClass1'}
})
export class App implements OnInit {
...
}

And if you use a class on the entry tag, Angular will merge the classes, i.e.,

<my-component class="someClass2">
  I have both someClass1 & someClass2 applied to me
</my-component>
Liam
  • 27,717
  • 28
  • 128
  • 190
JoshuaDavid
  • 8,861
  • 8
  • 47
  • 55
  • 1
    Love this for simplicity. However in my case the host element is encapsulated with a different attribute, let's call it `ngcontent_host` than any of the attributes on elements in my template`, let's call those `ngcontent_template`, so if I put a style in the `styleUrls` of my component, they won't affect the host element because they won't affect `ngcontent_host` , they can only affect template elements; they can only affect `ngcontent_template`. Am I mistaken? Any suggestions on this? I guess I could always turn `ViewEncapsulation.None` – Nate Anderson Mar 03 '17 at 18:38
  • Oops, maybe it's as Günter says: the special `:host` selector. [More info here](https://angular.io/docs/ts/latest/guide/component-styles.html#-host) – Nate Anderson Mar 03 '17 at 18:44
  • 15
    Another way is to just skip the variable, `@HostBinding('class.someClass') true;`. You can even do this from any class your component extends. – adamdport Nov 08 '17 at 19:26
  • 5
    To add multiple classes you can do host: { '[class]': '"class1 class2"' } – jbojcic Dec 11 '17 at 14:06
  • 4
    If you use the host: `{}` variant, you might want to set `use-host-property-decorator` setting to `false` in `tslint.json`. Otherwise you'll receive IDE warnings. @adamdport That method doesn't work (anymore). Using Angular 5.2.2 in our app. – Ruud Voost Jan 27 '18 at 13:06
  • If this is deprecated, is there still a way to do this statically? https://stackoverflow.com/questions/54011930/angular-2-how-to-statically-add-a-class-to-a-host-element – BBaysinger Jan 02 '19 at 19:14
  • 1
    Is it just me, or does the old way seem better than the new way? I'm sure they had good reason to migrate, but meh... – crush Jan 11 '19 at 19:04
  • 1
    Can anyone please 'prove' this is actually deprecated. Bacause I can't find anything on the website nor do I get a warning when using this in Angular7. It feels like this isn't deprecated, or Angular does a terrible job communicating it. @frosty please link us to a page that states it is deprecated. I feel like that's the least you can do. – Kerwin Sneijders May 28 '19 at 14:37
  • 1
    I deleted my comments. I was wrong. I also removed the comment from the answer to reduce confusion. – frosty Jun 18 '19 at 19:14
  • 4
    @adamdport Your answer will create a variable named "true", and the type will be "any". Not sure if that is what you intended? – jonas Apr 23 '20 at 07:22
  • @jonas it wasn't, but what you say appears to be the case. However, setting it to `false` has no effect! That is `@HostBinding('class.test') true = false;` will set the `test` class, but `@HostBinding('class.test') foo = false;` will not. Weird! – adamdport Apr 23 '20 at 13:38
  • 1
    Very clean syntax! Although it's not deprecated, it is advised against in the angular style guide rule [**06-03**](https://angular.io/guide/styleguide#style-06-03) *"Consider preferring the `@HostListener` and `@HostBinding` to the `host` property of the `@Directive` and `@Component` decorators."* – KyleMit Oct 21 '20 at 16:00
  • `@HostBinding('class.someClass') true;` is not valid anymore due to: https://github.com/angular/angular/issues/40979# I would go something like this instead: `@HostBinding('class.someClass') hostClass = () => true;` or `@HostBinding('class.someClass') private someClass = true;` – Véger Lóránd Aug 25 '21 at 11:49
48

You can simply add @HostBinding('class') class = 'someClass'; inside your @Component class.

Example:

@Component({
   selector: 'body',
   template: 'app-element'       
})
export class App implements OnInit {

  @HostBinding('class') class = 'someClass';

  constructor() {}      

  ngOnInit() {}
}
Mike D3ViD Tyson
  • 1,701
  • 1
  • 21
  • 31
  • 6
    The [*className* directive](https://malcoded.com/posts/angular-ngclass/) may also be used and it is better to avoid using `class` as a variable name (since you might reference it and change it later). Example: `@HostBinding('className') myTheme = 'theme-dark';`. – CPHPython Jan 16 '20 at 19:35
  • 1
    I would definitely avoid naming a variable "class". Some packages name it 'klass' or 'cssClass' – Mick Jun 24 '21 at 15:56
21

If you want to add a dynamic class to your host element, you may combine your HostBinding with a getter as

@HostBinding('class') get class() {
    return aComponentVariable
}

Stackblitz demo at https://stackblitz.com/edit/angular-dynamic-hostbinding

Saksham
  • 9,037
  • 7
  • 45
  • 73
10

NONE-VERBOSE (HOSTBINDING) PURE CSS solution


Another problem is that CSS has to be defined outside component scope, breaking component encapsulation

This is not true. With scss (SASS) you can easily style the component (itself;host) as so:

:host {
    display: block;
    position: absolute;
    width: 100%;
    height: 100%;
    pointer-events: none;
    visibility: hidden;

    &.someClass {
        visibility: visible;
    }
}

This way the encapsulation is "unbroken".

Youp Bernoulli
  • 5,303
  • 5
  • 39
  • 59
  • 1
    Since the other answers have different ways of setting the class for your component, if you use this way you can just set your class as a class on your component. So for the example in this answer it would be: – mkimmet Feb 24 '21 at 14:11
  • 1
    I like this answer. Exactly what I was looking for. Kudos! – Morfinismo May 19 '23 at 18:58
3

In addition to @JoshuaDavid answer, there is another way to define static class, which works on angular v8 when I tried (might also work on older versions):

@Component({
selector: "my-component.someClass1.someClass2",
...
})

which will generate following output:

<my-component class="someClass1 someClass2">
 ...
</my-component>

you can also just use this way:

@Component({
selector: ".someClass1.someClass2",
...
})

which will generate following output:

<div class="someClass1 someClass2">
 ...
</div>
Nuryagdy Mustapayev
  • 667
  • 1
  • 7
  • 27
  • if I specify `selector: "my-component.someClass1.someClass2",`, then I have to use the selector in HTML like ... – Tony Sep 15 '21 at 15:10
2

for multiple classes situation, as @jbojcic mentioned above, you can use:

host: {class: 'A B C'}

Joseph Wu
  • 4,786
  • 1
  • 21
  • 19
1

this is what I did:

import { Component, Attribute, HostBinding } from "@angular/core";

@Component({
    selector: "selector-el",
    template: ...                                            
})
export class MyComponent {
    @HostBinding('class') get classAttribute(): string {
        let defaultClasses = 'selector-el-class';
        return defaultClasses + ' ' + this.classNames;
    }

    constructor(
        @Attribute('class') public classNames: string
    ) { }
}
  • Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Oct 28 '21 at 05:51
1

Didn't see any answers talking about Renderer2 approach

This is what I have used, for example:

constructor(
  private readonly elementRef: ElementRef,
  private readonly renderer: Renderer2
) { }


@Input()
set disabled(disabled: boolean) {
  if (disabled) {
    this.renderer.addClass(this.elementRef.nativeElement, 'disabled');
  }
}

or inside ngOnInit

ngOnInit(): void {
    if (this.something.disabled) {
      this.renderer.addClass(this.elementRef.nativeElement, 'disabled');
    }
  }
O-9
  • 1,626
  • 16
  • 15
0

When you have multiple, host-bound classes I've found that the following usage of a HostBinding getter is most convenient:

@Component({ ... })
export class MyComponent {
    @Input()
    theme: 'success' | 'error';

    @HostBinding('class')
    get classes(): Record<string, boolean> {
        return {
            'my-component': true,
            'my-component-success-theme': this.theme == 'success',
            'my-component-error-theme': this.theme == 'error'
        }
    }
}

Even for trivial class bindings that have no corresponding logic, this is a nice way of managing all of your host classes in a single place.

@Component({ ... })
export class MyComponent {
    @HostBinding('class')
    get classes(): Record<string, boolean> {
        return {
            'my-component': true,
            'my-component-modifier': true
        }
    }
}
Jbird
  • 2,839
  • 1
  • 21
  • 28
-5

Here's how I did it (Angular 7):

In the component, add an input:

@Input() componentClass: string = '';

Then in the component's HTML template add something like:

<div [ngClass]="componentClass">...</div>

And finally in the HTML template where you instance the component:

<root componentClass="someclass someotherclass">...</root>

Disclaimer: I'm fairly new to Angular, so I might be just getting lucky here!

zippycoder
  • 2,140
  • 1
  • 13
  • 6
  • 3
    Slightly necro but: this does not add the CSS class to the host element - which is the element for the `` tag, not anything you add into the element's template. – millimoose Apr 30 '19 at 22:08
  • This answer is not related to the question. He needs to know how to add class to the host element. This is done by the host attribute in the @component decorator – Kyrolus Kamal Fahim Sous Jun 11 '23 at 19:10