javascriptangulartypescriptprimengprimeicons

Replace new PrimeNG Checkbox Icon dynamically at runtime with a Directive


I am looking for a work-around to replace the new Checkbox Check Icon, since angular + primeng updates the icons changed, but I want to use the older version of it.

Please find a minimal reproduction of my issue here.

My idea is to target all <p-checkbox> with a directive and replace the <checkicon> somehow.

I tried with viewContainerRef in the directives constructor, but then viewContainerRef targets the wrong html element (p-checkbox) and the icon is inserted in the wrong place.

@Directive({
  selector: 'p-checkbox',
})
export class IconDirective {
  constructor(private viewContainerRef: ViewContainerRef) {}
}

Then I tried to target PrimeNGĀ“s checkicon element directly as that is the place I want to replace the checkicon with my custom icon, but that does not seem to work.

@Directive({
  selector: 'checkicon',
})
export class IconDirective {
  constructor(private viewContainerRef: ViewContainerRef) {}

@HostListener('click', ['$event']) onModelChange(): void {
  setTimeout(() => {
    this.replaceCheckIcon();
  });
}

  async replaceCheckIcon() {
    this.viewContainerRef.clear();
    this.viewContainerRef.createComponent(CustomIcon);
  }
}

Following options won't work for me:

  1. the newly introduced checkboxIcon property, since I use the pi pi-times icon, but this icon changed in appearance.

  2. touching / changing any html directly, because in my project there are thousand of checkboxes and I won't add the <ng-template pTemplate="icon"> with my custom icon to all checkbox occurrences.

  3. adding <ng-container #target> to any of my html to have a viewContainerRef from a ViewChild query is also not an option, tho adding it programmatically would be an option, but I could not figure it out.


Solution

  • First you need to check if the old icon exists which I used querySelector, then I remove it using renderer2's removeChild method.

    Then we can use createComponent API to create the component. Then add the default primeng classes for the icon using addClass, then finally using appendChild insert the new icon where the old icon used to be.

    Hope you use this code and suit it to your requirements.

    import {
      Directive,
      ElementRef,
      HostListener,
      Renderer2,
      ViewContainerRef,
      createComponent,
      Injector,
      EnvironmentInjector,
      ChangeDetectorRef,
    } from '@angular/core';
    import { CustomIcon } from './icon/custom-icon';
    
    @Directive({
      selector: 'p-checkbox',
    })
    export class IconDirective {
      @HostListener('click', ['$event']) onModelChange(): void {
        setTimeout(() => {
          this.replaceCheckIcon();
        });
      }
    
      constructor(
        private elementRef: ElementRef,
        private viewContainerRef: ViewContainerRef,
        private renderer: Renderer2,
        private environmentInjector: EnvironmentInjector,
        private cdr: ChangeDetectorRef
      ) {}
    
      replaceCheckIcon() {
        const oldIcon = this.elementRef.nativeElement.querySelector('checkicon');
        const newIcon = this.elementRef.nativeElement.querySelector('custom-icon');
        const checkboxContainer =
          this.elementRef.nativeElement.querySelector('.p-checkbox-box');
        if (oldIcon) {
          this.renderer.removeChild(this.elementRef.nativeElement, oldIcon);
        }
        if (!newIcon) {
          const component = createComponent(CustomIcon, {
            environmentInjector: this.environmentInjector,
          });
          this.renderer.addClass(component.location.nativeElement, 'p-element');
          this.renderer.addClass(
            component.location.nativeElement,
            'p-icon-wrapper'
          );
          this.renderer.appendChild(
            checkboxContainer,
            component.location.nativeElement
          );
        }
      }
    }
    

    Stackblitz Demo