Angular @Input set programmatically for 3rd party controls

Viewed 467

I am using PrimeNG controls. I am setting the disabled attribute on various input controls via a directive and some of the controls are p-dropdown, p-listbox and p-calendar PrimeNG controls. The basic input, textarea and select controls work just find, but the PrimeNG controls have disable defined as @Input as follows:

private _disabled: boolean;

@Input() get disabled(): boolean {
    return this._disabled;
};

and a code snippet of the directive is as follows:

/*
** find all inputs and disable them.
*/
disableElements( el: ElementRef ): void {
    const controls = el.nativeElement.querySelectorAll( 'input, select, textarea, p-dropdown, p-listbox, p-checkbox' );
    controls.forEach( (elmt: any) => {
        this.disableElement( elmt );
    });
}
/*
** disable an input element.
*/
disableElement( elmt: any ): void {
    if( elmt.localName.substr(0,2) === 'p-' ) {
        this._renderer.setProperty(elmt, 'disabled', 'true');
    } else {
        if(!elmt.hasAttribute('disabled')) {
            this._renderer.setAttribute(elmt, 'disabled', 'true');
        }
    }
}

So, how to disable the PrimeNG 3rd party controls given a ElementRef.nativeElement? I am thinking, I need a way to fulfill the disabled @Input property given an element reference.

1 Answers

Basically I don't think that it's a good idea to disable controls programmatically using a directive, I totally recommend you to handle it using good-old inputs instead.

But I can propose a following solution for your situation: https://stackblitz.com/edit/angular-disable-prime-ng-elements

The solution itself is not perfect - but it's a tradeoff if you don't want to work with disabled inputs.

  1. Firstly, create a directive called PrimeNgComponentsChangeDetectorRefDirective to get a public version of primeng components' change detector to update their views. You should manually include all the primeng components that you want to handle. The trick here is to use the same selector to automatically match primeng components.
@Directive({
  selector: 'p-checkbox, p-dropdown, p-listbox'
})
export class PrimeNgComponentsChangeDetectorRefDirective {
  public hostComponent: Checkbox | Dropdown | Listbox;

  constructor(
    public cdr: ChangeDetectorRef,
    @Optional() private checkbox: Checkbox,
    @Optional() private dropdown: Dropdown,
    @Optional() private listbox: Listbox
  ) {}

  ngAfterViewInit() {
    this.hostComponent = this.getHostComponent();
  }

  private getHostComponent() {
    return [this.checkbox, this.dropdown, this.listbox].find(
      component => !!component
    );
  }
}
  1. Then, in your disabling directive implement following properties and methods:
  @ContentChildren(PrimeNgComponentsChangeDetectorRefDirective)
  private componentsQuery: QueryList<
    PrimeNgComponentsChangeDetectorRefDirective
  >;

  disablePrimeNgComponents() {
    const components = this.componentsQuery.toArray();

    components.forEach(component => {
      component.hostComponent.disabled = true;
      component.cdr.detectChanges();
    });
  }

We need to detectChanges manually because we have changed disabled property programmatically and angular knows nothing about this change.

Related