Using a directive to add class to host element [duplicate]
Asked Answered
P

4

61

I am currently learning Angular 2. I understood how to use the Angular Renderer to set an ElementStyle, but now I would like to use the Renderer method:

setElementClass(renderElement: any, className: string, isAdd: boolean) : void

My question is how can I import a CSS class to my attribute directive? Do I have to convert my CSS class to JSON?

Pshaw answered 22/9, 2016 at 12:29 Comment(1)
What do you mean by importing a css class? className is string so why you have to load or convert or import css class?Zenobia
J
126

Original OP was asking how to use Renderer. I've included the @HostBinding for completeness.

Using @HostBinding

To add a class to an element you can use @HostBinding

import { Directive, HostBinding} from '@angular/core';

@Directive({
  selector: '[myDirective]',
})
export class MyDirective {

  @HostBinding('class')
  elementClass = 'custom-theme';

  constructor() {
  }
}

Using @HostBinding with multiple classes

To make multiple classes more comfortable to use, you can use the ES6 getter and join the classes together before returning them:

import { Directive, HostBinding} from '@angular/core';

@Directive({
  selector: '[myDirective]',
})
export class MyDirective {
  protected _elementClass: string[] = [];

  @Input('class')
  @HostBinding('class')
  get elementClass(): string {
      return this._elementClass.join(' ');
  }
  set(val: string) {
      this._elementClass = val.split(' ');
  }

  constructor() {
      this._elementClass.push('custom-theme');
      this._elementClass.push('another-class');
  }
}

Using Renderer

The more low level API is Renderer2. Renderer2 is useful when you have a dynamic set of classes you would like to apply to an element.

Example:

import { Directive, ElementRef, Renderer2 } from '@angular/core';

@Directive({
  selector: '[myDirective]',
})
export class MyDirective {

  constructor(private renderer: Renderer2, hostElement: ElementRef) {
    renderer.addClass(hostElement.nativeElement, 'custom-theme');
  }
}
Jelsma answered 6/5, 2017 at 12:43 Comment(6)
I'm using for loop of classes array, is that practical?Emelun
For multiple classes you should perform a loop. If you take a look at the source you'll see Angular calls the classList.add method. While this native method supports multiple arguments, the Angular implementation only allows passing one class name at a time.Jelsma
pushing classes from the constructor doesn't seem to work. But it works from ngAfterViewInitPotentiality
The setter never seems to get called, causing any initial classes of the host to be lost :-(Riesman
You don't need to DI the renderer or manipulate the existing classes instead use @HostBinding('class.your-class') - https://mcmap.net/q/121937/-how-to-add-remove-class-from-directiveAquavit
I am trying to remove the class in directive 'error' HostListener. Suprisingly for me, the remove is successfull, but the layout is not changed - the removed CSS class is still present there.Gyratory
B
13

Why would you want to use the Renderer or Renderer2 class? The preferred way to do this in a directive is to use the @HostBinding decorator.

Example:

import { HostBinding } from '@angular/core';

@Directive({
    selector: '[myDirective]'
})
export class MyDirective {

    @HostBinding('class')
    className = 'my-directive-css-class';
}
Bisectrix answered 13/9, 2017 at 2:46 Comment(4)
This is useful if you want to add a class based on some other property, i.e. dynamically:Publican
I think this is only useful to completely replace the classes that may already be defined in the HTML. How would I add a class with this method? Renderer seems more flexible.Mitosis
This syntax is better : @HostBinding('class.isPlaying') class_isPlaying = true; (see this https://mcmap.net/q/121328/-hostbinding-with-a-variable-class-in-angular). It will toggle classes as opposed to just wiping out the whole attribute.Mitosis
@Mitosis That syntax probably was better, and still is a good choice if you need to toggle one class based on a property. It seems however that in recent versions of Angular (e.g. 10) the @HostBinding('class') does not wipe out the whole attribute. It behaves just like the [class] binding in the template, in that you can give it classes as a space delimited string, an array of strings, or even conditional classes as an object (note however, that it does not detect changes within an array or object). ref: angular.io/guide/…Mansuetude
K
10

Just another way to do it but easier and more understandable to me.

You let me know what you think

import { Directive, Input} from '@angular/core';

@Directive({
  selector: '[whatever]',
  host: {
    // These are like ngClass class condition values
    '[class.custom-class1]': 'true', // Default class for example
    '[class.custom-class2]': 'foo === "expectedValue"', // Predicate1
    '[class.custom-class3]': 'foo !== "expectedValue"', // Predicate2
  },
})
export class WhateverDirective {
  @Input() foo: string;
}
Kohlrabi answered 23/10, 2019 at 12:26 Comment(1)
eslint thinks otherwise as of 12/2022Saucer
Z
6

Example of how to use Renderer and ElementRef to add css class to element.

@Directive({
   selector: '[whatever]'
})
class WhateverDirective {
   constructor(renderer: Renderer, el: ElementRef) {
       renderer.setElementClass(el.nativeElement, 'whatever-css-class', true);
   }
}

The whatever-css-class is defined in a css file, which is referenced in html

Zenobia answered 22/9, 2016 at 12:56 Comment(4)
Yes absolutely, so far I understood the setElementClass function, but where lays the whatever-css-class, an attribute directive doesn't have an css file or is there a possibility to add one ?Pshaw
Right, so what you can do is define class in css file which you are referencing in html, so that way it will be applied to the element.Zenobia
Renderer class is now deprecated. Use Renderer2 instead.Berceuse
You don't need to DI the renderer instead use @HostBinding('class.your-class') - https://mcmap.net/q/121937/-how-to-add-remove-class-from-directiveAquavit

© 2022 - 2024 — McMap. All rights reserved.