rx-angular icon indicating copy to clipboard operation
rx-angular copied to clipboard

rxStyle or rxClass as an alternative of style and class directives

Open galczo5 opened this issue 3 years ago • 4 comments

Problem Solved By The Feature

Using [style.css-property]="value" or [class.className]="value" in Angular requires change detection to apply the changes. When we want to change a style of only one html element it's a lot of unnecessary code to execute.

Solution

Would be great to have rxStyle and rxClass directives. It can be implemented using Renderer2 instance. For example (very dirty, but it shows the basic usage):

import {Directive, ElementRef, Input, OnChanges, OnDestroy, Renderer2, SimpleChanges} from "@angular/core";
import {Observable, Subject, takeUntil} from "rxjs";

@Directive({
  selector: '[rxClass]'
})
export class RxClassDirective implements OnDestroy, OnChanges {

  @Input()
  cssClass!: string = '';

  @Input()
  stream$!: Observable<void>;

  private readonly destroy$: Subject<void> = new Subject<void>();

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

  ngOnChanges(changes: SimpleChanges) {
    if (changes.stream) {
      this.destroy();
      if (changes.stream.currentValue) {
        this.onStreamChanges();
      } else {
        this.renderer.addClass(this.elementRef.nativeElement, this.cssClass);
      }
    }
  }

  ngOnDestroy() {
    this.destroy();
  }

  private onStreamChanges() {
    this.stream$
      .pipe(takeUntil(this.destroy$))
      .subscribe(value => {
        if (value) {
          this.renderer.addClass(this.elementRef.nativeElement, this.cssClass);
        } else {
          this.renderer.removeClass(this.elementRef.nativeElement, this.cssClass);
        }
      });
  }

  private destroy() {
    this.destroy$.next();
    this.destroy$.complete();
  }

}

Final version should have nicer API, but the main idea is the same. Another problem is changing styles of a host component. The possible implementation is to use a service provided in a component instead of @HostBinding decorator. From Angular 14 there's another, (in my opinion) cooler way of doing it. It's possible to create hook-like function which will provide object containing current value and a setter. I described it here: https://galczo5.github.io/mythical-angular/posts/togglers/.

Both service and hook-like function for me are better than triggering unnecessary change detection.

Alternatives Considered

We can always use Renderer2 in component body. Not a very clever way of implementing it.

Additional Context

A lot of developers are still using the Angular directives and triggers change detection, when they want to change only one style/class on one element. Having it in lib like rxAngular would be great start for them to migrate to more efficient code.

galczo5 avatar Jul 05 '22 13:07 galczo5

Hi @galczo5, there is already an opened PR for a rxClass directive (#1041 and #1048).

edbzn avatar Jul 06 '22 07:07 edbzn

Nice! Thx for info. I see that it's opened since 28 Oct 2021. Do you need any help with it?

galczo5 avatar Jul 06 '22 08:07 galczo5

Yeah, there is some stuff waiting in the queue, our priority is to work on the template and cdk first stable release before introducing new stuff, but any help is always appreciated!

edbzn avatar Jul 06 '22 08:07 edbzn

RxClass and RxStyle are both in development now. just added the RxStyle PR: https://github.com/rx-angular/rx-angular/pull/1465

with the new directive composition API this is gonna be glorious

hoebbelsB avatar Nov 24 '22 22:11 hoebbelsB