@HostBinding with a variable class in Angular
Asked Answered
C

8

70

I have this code which sets a class on the host:

@HostBinding('class.fixed') true;

What I would like to do is make this a variable class that I can modify. How can I do this?

Clerestory answered 3/2, 2016 at 4:44 Comment(1)
For future visitors of this question, the snippet in the question above only works due to a bug in Angular which will be fixed in future versions: github.com/angular/angular/issues/40220Hindermost
K
118

This can't be made variable.

What you can do instead is to bind to the class property directly

@HostBinding('class') classes = 'class1 class2 class3';
Kenon answered 3/2, 2016 at 5:5 Comment(12)
What's the downvote for? Did it not work? There was some change since then that caused problems with binding to [class]="..." that might hit this approach as well.Parsonage
You sir, you are a life saviour!Mozza
To build on @GünterZöchbauer's example, could have more dynamic classes via @HostBind('class') hostClasses: string; initially then in ngOnInit (and optionally in ngOnChanges for more dynamism): this.hostClasses = this.getHostClasses(); and have whatever logic you'd want in the getHostClasses method, possibly pulling from some other @Input() values.Scholl
This works, but the issue is it overrides any classes added to the host element from the parent componentSanctitude
That's a known issue and I think unlikely to change.Parsonage
@GünterZöchbauer, is this issue raised somewhere on GitHub? Could you please share a link to the issue?Maury
@Maury the answer is quite old already. I haven't followed Angular closely since quite a while and don't know if this is still the case or if it is planned or even possible to be changed.Parsonage
@GünterZöchbauer - I just have faced this case (host object appends classes, but HostBinding replaces all other classesErythroblastosis
@Erythroblastosis If you think it's a bug, please create an issue if you can't find an existing one.Parsonage
@GünterZöchbauer -I've created an issue (github.com/angular/angular/issues/26251) - will see how it goes.Erythroblastosis
If it helps this is a good example of using @HostBinding('class.your-class') - https://mcmap.net/q/121937/-how-to-add-remove-class-from-directiveExtend
@RyanSilva this is not an issue anymore as with angular 13. I did not check the exact version when this was changed. The current behavior is, that classes set directly to the element in the parent template are merged with the @HostBinding definitions. However, multiple @HostBinding('class') in the component are still conflicting.Pageantry
C
52

If you have a limited number of classes you can conditionally add each one:

@HostBinding('class.c1') get c1 () { return this.useC1; } 
@HostBinding('class.c2') get c2 () { return this.useC2; }

Note that .c1 and .c2 need to be defined outside the component.

Plunker

Calorimeter answered 3/2, 2016 at 16:55 Comment(3)
I needed to added () for it to work (e.g. get c1() { return this.useC1; }.Corral
You shouldn't need to define .c1 and .c2 outside of the component if you use the :host selector. See this answer for an example: #35169183Holily
For me, it worked after adding 'get' keyword before method name. Thanks:)Sympetalous
B
40

I have to contradict the other answers, there is no reason why binding class.foo shouldn't work. Actually, the following format works properly:

@HostBinding('class.foo') variableName = true;

A common gotcha that foils many developers is the scope of the CSS class. You might need to change the selector scope in order to make the class visible to the selector itself, and not its children (you can see a discussion here).

To make the class visible to the Angular component itself you have two choices: you can either change the View Encapsulation to None (it will alter the behavior), or you can match the class with the :host pseudoselector.

In other words, the problem is that HostBinding only sees the host scope, it means that it only sees the classes and the id applied to the component itself, not to its children. So when you write your CSS, you need to specify that CSS belongs to the component itself (the host pseudoelement).

According to Angular documentation:

Use the :host pseudo-class selector to target styles in the element that hosts the component (as opposed to targeting elements inside the component's template).

You can easily specify the host scope just adding :host before your CSS rule:

:host.foo { // It only matches <component-name class="foo">
  /* Your CSS here */
}

In place of

.foo { // It matches any <element class="foo" /> inside <component-name>
  /* Your CSS here */
}

If you want, I created a working Plunker that you can see clicking here

Bugaboo answered 13/9, 2017 at 21:56 Comment(2)
I don't believe you need the parenthesis around (.className) when chaining it off :host. I think you make chain just like any other selector string. :host.classNameHolily
This is for a fixed class name though, not a variable class name.Radioactive
M
17

Günter's answer isn't really helpful in case of already having some class names bound to a variable.

A good way to combine variable string class names with boolean style predefined class names is to use the classnames npm package.

Use it together with the @HostBinding and a setter function to get amazing results:

import * as classNames from 'classnames';

(...)

@HostBinding('class') get classes(): string {
  return classNames(this.getDynamicClassName(), {
    'is-open': this.isOpen,
    'has-children': this.hasChildren
  });
}
Mcgannon answered 27/3, 2018 at 20:38 Comment(0)
R
11
@Input()
  class = '';

@HostBinding('attr.class')
get btnClasses() {
return [
    'btn',
    this.someClassAsString,
    this.enableLight ? 'btn-secondary-light' : '',
    this.class,
].filter(Boolean).join(' ');
};

You can hijack the class attribute with an input and add what you need to it in a hostbinding

Refuse answered 21/6, 2019 at 21:18 Comment(1)
This is a great answer, solves #35169183 IMHO in a better wayCookbook
S
2

You can create some separated directives with class.

For example: I have a button in my page, and has may states: default, primary, danger and fluid. Button can have many different states, but I'll show you with theese three states because of huge amount of code. So let's get start it!

button.ts

//default button

@Directive({
    selector: '[appButtonDefault]'
})
export class ButtonDefaultDirective {

    // the name of the field is not important
    // if you put this directive to element, 
    // this element will have the class called 'button--default'

    @HostBinding("class.button--default")
    private defaultClass: boolean = true;
}


//primary button

@Directive({
    selector: '[appButtonPrimary]'
})
export class ButtonPrimaryDirective {

    // the name of the field is not important
    // if you put this directive to element, 
    // this element will have the class called 'button--primary'

    @HostBinding("class.button--primary")
    private primaryClass: boolean = true;
}


// danger button

@Directive({
    selector: '[appButtonDanger]'
})
export class ButtonDangerDirective {

    // the name of the field is not important
    // if you put this directive to element, 
    // this element will have the class called 'button--primary'

    @HostBinding("class.button--danger")
    private dangerClass: boolean = true;
}

@Directive({
    selector: '[appButtonFluid]'
})
export class ButtonFluidDirective {

    // the name of the field is not important
    // if you put this directive to element, 
    // this element will have the class called 'button--primary'

    @HostBinding("class.button--fluid")
    private fluidClass: boolean = true;
}


// you need to also create a component class,
// that import styles for this button
@Component({
    //just put created selectors in your directives
    selector: `[appButtonDefault], [appButtonPrimary], 
               [appButtonDanger], [appButtonFluid]`,
    styleUrls: ['<-- enter link to your button styles -->'],

    // it is required, because the content of <button> tag will disappear
    template: "<ng-content></ng-content>" 
})
export class ButtonComponent {}


// you don't have to do it, but I prefet to do it

@NgModule({
    declarations: [
        ButtonDefaultDirective,
        ButtonPrimaryDirective,
        ButtonDangerDirective,
        ButtonFluidDirective,
        ButtonComponent
    ],
    exports: [
        ButtonDefaultDirective,
        ButtonPrimaryDirective,
        ButtonDangerDirective,
        ButtonFluidDirective,
        ButtonComponent
    ]
})
export class ButtonModule {}

Don't forget to import ButtonModule to your app.module.ts file. It is very important.

app.component.html

<!-- This button will have the class 'button--default' -->
<button appButtonDefault>Default button</button>

<!-- But this button will have the class 'button--primary button--fluid' -->
<button appButtonPrimary appButtonFluid>Primary and fluid</button>

I hope it helps.

Skinnydip answered 1/10, 2017 at 15:37 Comment(3)
I hope this approach is not been used in your production code... Lot of code instead of one line <button class="button--danger">...</button>Vashtivashtia
Excellent answer! ThanksVibrations
@Vashtivashtia From this perspective it looks, that there are lot's of coding. Your solution is also good due to not required additional directives, but you know: if you create those directives, then you don't have to remember class names. Instead: you can use directive names. This causes better validation. If you provide wrong directive name, then app won't start. Different thing is for adding classes directly.Skinnydip
T
2

There are lots of answers already, but none have mentioned NgClass. In my opinion, the most reliable and consistent way to do this is extending NgClass because it provides everything we need out-of-the-box-ish:

@Directive({ selector: '[myDirective]'})
export class MyDirective extends NgClass {
  constructor(
    _iterableDiffers: IterableDiffers,
    _keyValueDiffers: KeyValueDiffers,
    _ngEl: ElementRef,
    _renderer: Renderer2
  ) {
    super(_iterableDiffers, _keyValueDiffers, _ngEl, _renderer);
  }

  setClass() {
    this.ngClass = {
      underline: true,
      bold: true,
      italic: true,
      pretty: false
    };

    // or
    this.ngClass = ['asd', 'abc', 'def'];

    // or 
    this.ngClass = 'foo';
  }
}
Traditionalism answered 12/3, 2021 at 12:21 Comment(0)
L
0

Easiest way would be like this

@Directive({
    selector: '[appSomeDirective]',
    host: {
        class: 'some-directive-classname another-classname',
    },
})
export class SomeDirective {}
Leatherwood answered 21/11, 2023 at 10:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.