Issue
I'm using ng-bootstrap to display an error tooltip for a bunch of input fields inside a form. Since most of the code is repeated, I thought about creating a directive to set the attributes to the elements based on some few parameters:
From this:
<div class="value" tooltipClass="tooltip-error"
[ngbTooltip]="'getError(form, 'serial')'"
[openDelay]="300" [closeDelay]="500"
[ngClass]="{ 'is-invalid': submitted && form.get('serial').errors }">
<input formControlName="serial" type="text" placeholder="Serial Number" />
</div>
To this:
<div class="value" [form]="form" [submitted]="submitted" [controlName]="'serial'">
<input formControlName="serial" type="text" placeholder="Serial Number" />
</div>
Using this directive:
import { Directive, Input, HostBinding } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { formError } from '@core/helpers';
@Directive({
selector: '[submitted][form][controlName],[submitted][form][controlName][disabled]',
host: {
'tooltipClass': 'tooltip-error',
'[attr.openDelay]': '300',
'[attr.closeDelay]': '500',
'[class.is-invalid]': 'submitted && form.get(controlName).errors',
'[class.not-allowed-cursor]': 'disabled',
'[class.no-text-selection]': 'disabled',
}
})
export class FormControlDirective {
_controlName: string = '';
_form!: FormGroup;
_submitted!: boolean;
@Input()
public get submitted(): boolean {
return this._submitted;
}
public set submitted(v : boolean) {
this._submitted = v;
this.resolveTooltip();
}
@Input()
public get form(): FormGroup {
return this._form;
}
public set form(v : FormGroup) {
this._form = v;
this.resolveTooltip();
}
@Input()
public get controlName(): string {
return this._controlName;
}
public set controlName(v : string) {
this._controlName = v;
this.resolveTooltip();
}
@Input() disabled: boolean = false;
@HostBinding('[attr.ngbTooltip]')
ngbTooltip: string = '';
resolveTooltip() {
//This method gets an error message based on the type of error.
//I tried to put the value between ' ', with no avail.
this.ngbTooltip = formError(this.submitted, this.form, this.controlName);
}
}
First, I noticed that the value in the HostBinding does not update automatically, the same way as the host
attributes, so I created getters/setter.
But still, the tooltip does not appear. If I add the attributes manually, the tooltip appears normally.
Here's the rendered result of the manual way:
<div tooltipclass="tooltip-error" class="value is-invalid" ng-reflect-tooltip-class="tooltip-error" ng-reflect-ngb-tooltip="Error example" ng-reflect-open-delay="300" ng-reflect-close-delay="500" ng-reflect-ng-class="[object Object]">
<input formcontrolname="serial" type="text" placeholder="Serial Number" ng-reflect-name="serial" class="ng-dirty ng-invalid ng-touched">
</div>
And here's the rendered result of using a directive:
<div tooltipclass="tooltip-error" class="value is-invalid" ng-reflect-form="[object Object]" ng-reflect-submitted="true" ng-reflect-control-name="serial" opendelay="300" closedelay="500" ngbtooltip="'Serial number is required'">
<input formcontrolname="serial" type="text" placeholder="Serial Number" ng-reflect-name="serial" class="ng-dirty ng-invalid ng-touched">
</div>
From what I could understand, it looks like the attributes are not being "parsed", not appearing as ng-reflect-
.
Trying to simply add the name of the attributes as starting with ng-reflect-
didn't work. :)
What's going on and how to fix it?
Edit:
I think that I understand what's happening.
I'm trying to trigger the selector of another directive ([ngbTooltip]
, from ng-boostrap
) within my custom directive.
If I create something simpler, like this:
'[attr.ngbTooltip]': 'tooltipText',
//...
tooltipText: string = '';
//...
resolveTooltip() {
this.tooltipText = formError(this.submitted, this.form, this.controlName);
}
I'll get an attribute named as ngbTooltip=""
instead of [ngbTooltip]=""
which is the trigger for the ngbTooltip
from ng-bootstrap
.
Is there any way to trigger a selector of another directive from a custom directive?
The answer?
This looks really wrong, but it works. I can't think anymore today.
What I did was to extend from ngbTooltip
and set the values to the internal properties that are used by the tooltip.
import { Directive, Input, OnInit } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { formError } from '@core/helpers';
import { NgbTooltip } from '@ng-bootstrap/ng-bootstrap';
@Directive({
selector: '[submitted]',
host: {
'[class.is-invalid]': 'submitted && form.get(controlName).errors',
'[class.not-allowed-cursor]': 'disabled',
'[class.no-text-selection]': 'disabled'
}
})
export class FormControlDirective extends NgbTooltip implements OnInit {
_controlName: string = '';
_form!: FormGroup;
_submitted: boolean = false;
@Input()
public get submitted(): boolean {
return this._submitted;
}
public set submitted(v : boolean) {
this._submitted = v;
this.resolveTooltip();
}
@Input()
public get form(): FormGroup {
return this._form;
}
public set form(v : FormGroup) {
this._form = v;
this.resolveTooltip();
}
@Input()
public get controlName(): string {
return this._controlName;
}
public set controlName(v : string) {
this._controlName = v;
this.resolveTooltip();
}
@Input() disabled: boolean = false;
ngOnInit(): void {
this.tooltipClass = 'tooltip-error';
this.openDelay = 300;
this.closeDelay = 500;
super.ngOnInit();
}
resolveTooltip() {
this.ngbTooltip = formError(this.submitted, this.form, this.controlName);
}
}
Solution
Tim Deschryver adressed a similar use case here with PrimeNg directive :
https://dev.to/this-is-angular/use-angular-directives-to-extend-components-that-you-dont-own-1jio
In my opinion, just applying an attribute of a directive through a custom directive is not a nice way, due to the strong link you would create, but I don't say that this could not work at all.
Tim solution looks nice, but you need to map the ng-bootstrap existing directive, instead of the ones you mapped.
Your solution to extend the class looks quite "edge" like you say ;) Cheers !
Answered By - Alain Boudard
0 comments:
Post a Comment
Note: Only a member of this blog may post a comment.