#Updating HTMLElement classes in a directive

9 messages · Page 1 of 1 (latest)

finite ravine
#

Hello everyone! I'm trying to update an element class value in a directive. This is my code:

@Directive({
  selector: 'button[appButton], a[appButton]',
  standalone: true,
})
export class ButtonDirective implements OnInit, OnChanges {
  @Input() props: VariantProps<typeof buttonVariants> = {};
  @Input() className: string | undefined;

// buttonVariants() returns TailwindCSS classes
  elementClass = signal(buttonVariants());

  constructor(private elementRef: ElementRef<HTMLElement>) {}

  ngOnInit(): void {
    this.elementRef.nativeElement.className = this.elementClass();
  }

  ngOnChanges(): void {
    this.elementClass.set(
      buttonVariants({ ...this.props, className: this.className })
    );
    this.elementRef.nativeElement.className = this.elementClass();
  }
}

Here, the function buttonVariants() returns a string with a bunch of Tailwind classes.
There are a few things that are bugging me:

  1. If I remove the ngOnInit method, the elements that uses this directive without props don't get the classes. I thought ngOnChanges method runs everytime there are inputs and they change, even when they have a default value. Is this not the case?

  2. If I remove the signal and equal the element class directly to buttonVariants() fn and update the value in ngOnChanges, the component don't update their view when changing the value of the inputs. Why is a signal necessary here?

This directive is inspired by @shadcn/ui. Even if it works like this, I'm trying to improve the code. Anyone got any ideas?
Here is a stackblitz demo: https://stackblitz.com/edit/obyfkw-h59ftc?file=src%2Fapp%2Fapp.component.html,src%2Fapp%2Fbutton%2Fvariants.ts,src%2Fapp%2Fbutton%2Fbutton.directive.ts

StackBlitz

Angular Example - Getting Started

lofty snow
#

Called before ngOnInit() (if the component has bound inputs) and whenever one or more data-bound input properties change.

#

As there are no bound inputs, its not called.

#
  1. Not sure what u mean, this works fine for me:
  ngOnInit(): void {
    this.elementRef.nativeElement.className = buttonVariants({
      ...this.props,
      className: this.className,
    });
  }

  ngOnChanges(): void {
    this.elementRef.nativeElement.className = buttonVariants({
      ...this.props,
      className: this.className,
    });
  }
#

In case u are using

  ngOnInit(): void {
    this.elementRef.nativeElement.className = buttonVariants();
  }

  ngOnChanges(): void {
    this.elementRef.nativeElement.className = buttonVariants({
      ...this.props,
      className: this.className,
    });
  }

It's expected for the buttons to not have the classes. As ngOnInit runs after the ngOnChanges, meaning u are resetting them to the default

#

In ngOnInit u also need to account for the inputs.

#

So there is no need for the signal to make it work. The code is exactly doing what it says its doing (assuming you are doing the above)

finite ravine