22

Using a custom directive how would you add/remove a class on the host element based on a specific conditions?

Example:

@Directive({
  selector: '[customDirective]'
})
export class CustomDirective {
  constructor(service: SomService) {
    // code to add class

    if (service.someCondition()) {
        // code to remove class
    }
  }
}
mtpultz
  • 17,267
  • 22
  • 122
  • 201
Napas
  • 2,692
  • 3
  • 28
  • 33
  • 1
    I think you know how to add styles with host binding but classes are not supported inside directives I guess :/ http://stackoverflow.com/questions/35915433/angular2-styles-in-a-directive#comment59489878_35915497 – eko Jan 07 '17 at 11:11

4 Answers4

36

If you don't want to use the ngClass directive (Hint: you can pass a function to [ngClass]="myClasses()" if it would be to messy inline in your template) you can just utilize the Renderer2 for it to add one or more classes:

export class CustomDirective {

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

   addClass(className: string, element: any) {
       this.renderer.addClass(element, className);
       // or use the host element directly
       // this.renderer.addClass(this.elementRef.nativeElement, className);
   }

   removeClass(className: string, element: any) {
       this.renderer.removeClass(element, className);
   }

}
alexKhymenko
  • 5,450
  • 23
  • 40
malifa
  • 8,025
  • 2
  • 42
  • 57
17

When you are using directives in Angular you would want to use @HostBinding, and bind to class.your-class in order to be able to add/remove your class based on a predicate. You don't need to DI in the Renderer2 to effectively add/remove classes.

For example, when using Bootstrap and Reactive Forms and you want to indicate a valid or invalid form field you can do something like:

import { Directive, Self, HostBinding, Input } from '@angular/core';
import { NgControl } from '@angular/forms';

@Directive({
  selector: '[appCheckFormFieldValidity]'
})
export class CheckFormFieldValidity{
  @Input() public class: string;

  constructor(
    @Self() private ngControl: NgControl
  ) { }

  @HostBinding('class.is-valid')
  public get isValid(): boolean {
    return this.valid;
  }

  @HostBinding('class.is-invalid')
  public get isInvalid(): boolean {
    return this.invalid;
  }

  public get valid(): boolean {
    return this.ngControl.valid && 
    (this.ngControl.dirty || this.ngControl.touched);
  }

  public get invalid(): boolean {
    return !this.ngControl.pending &&
      !this.ngControl.valid &&
      (this.ngControl.touched || this.ngControl.dirty);
  }
}

This is not a rigorous example, but it illustrates the use of @HostBinding, and I created the example in a StackBlitz

mtpultz
  • 17,267
  • 22
  • 122
  • 201
  • Unfortunately, this example won't work for structural directives and classes will have to be manually added in. – G3tinmybelly Oct 23 '19 at 15:12
  • Unfortunately, this require you have those classes as global css classes. since directive doesn't support scoped css (https://github.com/angular/angular/issues/17766) – Huantao Jul 06 '22 at 19:40
3

Directive example for opening and closing toggle on dropdown

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

@Directive({
    selector: '[appDropDown]',
})
export class DropsownDirective{

@HostBinding('class.open') isopen = false;
@HostListener('mouseenter') onMouseEnter(){
this.isopen = !this.isopen;
}
@HostListener('mouseleave') onMouseLeave(){
    this.isopen = !this.isopen;
}
}

Component add directive appDropDown

<div class="col-xs-12">
        <div class="btn-group" appDropDown>
        <button class="btn btn-primary dropdown-toggle">
            Manage Movie <span class="caret"></span>
        </button>
        <ul class="dropdown-menu">
            <li><a href="#">To watching List</a></li>
            <li><a href="#">Edit Movie</a></li>
            <li><a href="#">Delete Movie</a></li>
        </ul>
    </div>

Make sure to Include new directive in the @NgModule declarations

Lijo
  • 6,498
  • 5
  • 49
  • 60
1
export class CustomDirective {
   classname:string = "magenta";

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

   addClass(className: string, element: any) {
        // make sure you declare classname in your main style.css
        this.renderer.addClass(this.elementRef.nativeElement, className);
   }

   removeClass(className: string, element: any) {
       this.renderer.removeClass(this.elementRef.nativeElement,className);
   }

}