I am creating an Angular directive to be used on <input type='text' formControlName="queryInput"> elements to display validation messages for the formControl.
The idea is:
If an
[errorTemplateRef]="errorTemplate"is passed to the directive, it should use the provided template to display the error message, positioning the template where it was declared.If no
errorTemplateRefis provided, the directive should add a simple<div>Validation error</div>right after the input element.
I have no issues implementing point 2. However, I am facing problems with point 1. I can't figure out how to 'render' the template while passing it a context (the error message) through the directive.
Could you please help me with this issue?
Angular Template:
<form
[formGroup]="formGroup"
(ngSubmit)="onSubmitSearch()"
class="flex w-full shadow-default"
>
<input
formControlName="queryInput"
type="text"
class="w-full h-full border-0 bg-white rounded-l-md"
appInput
[showError]="showError"
[errorTemplateRef]="errorTemplate"
/>
<button
type="submit"
class="flex justify-center items-center w-29 ml-1 p-3 bg-white rounded-r-md"
>
<span class="w-3/6 text-blue font-semibold text-sm tracking-wide uppercase">Cerca</span>
<app-icon
class="w-3/6 text-indaco"
name="mdi:magnify"
size="16"
></app-icon>
</button>
</form>
<ng-template
#errorTemplate
let-message
><p class="text-crimson-red">Error: {{ message }}</p>
</ng-template>
appInput Directive - in this moment it replace a ng-tamplate with a simple , but i want to use it
@Directive({
selector: '[appInput]',
standalone: true,
})
export class AppInputDirective implements OnInit, OnChanges, OnDestroy {
@Input() showError = false;
@Input() errorTemplateRef: TemplateRef<unknown> | undefined;
private _errorElement: HTMLElement | undefined;
private _errorPlaceholderElement: HTMLElement | undefined;
private _errorSubscription: Subscription | undefined;
constructor(
private _el: ElementRef<HTMLInputElement>,
private _renderer: Renderer2,
private _ngControl: NgControl,
) {}
ngOnInit(): void {
// If there's a placeholder to display the error, use it;
// otherwise, create a div element below the input.
this._errorPlaceholderElement = <HTMLElement>this.errorTemplateRef?.elementRef.nativeElement;
const inputParent = <HTMLElement>this._renderer.parentNode(this._el.nativeElement);
// Create a div element for the error message.
this._errorElement = <HTMLElement>this._renderer.createElement('div');
this._renderer.addClass(this._errorElement, 'text-coral-red');
const wrapperElement = <HTMLElement>this._renderer.createElement('div');
if (!this._errorPlaceholderElement && inputParent) {
const wrapperClasses = ['flex', 'flex-col', 'gap-1'];
inputParent.classList.contains('w-full') && wrapperClasses.push('w-full');
inputParent.classList.contains('h-full') && wrapperClasses.push('h-full');
wrapperElement.classList.add(...wrapperClasses);
this._renderer.insertBefore(inputParent, wrapperElement, this._el.nativeElement);
this._renderer.appendChild(wrapperElement, this._el.nativeElement);
// Insert the error element after the target element.
this._renderer.insertBefore(inputParent, this._errorElement, this._el.nativeElement.nextSibling);
}
// If there's a placeholder, position the error in its place.
if (this._errorPlaceholderElement) {
// Replace the original element with the new one.
const parent = <HTMLElement>this._renderer.parentNode(this._errorPlaceholderElement);
this._renderer.insertBefore(parent, this._errorElement, this._errorPlaceholderElement);
this._renderer.removeChild(parent, this._errorPlaceholderElement);
}
}
ngOnChanges(changes: SimpleChanges): void {
if (changes['showError']?.currentValue) {
this._errorSubscription = this._ngControl.statusChanges
?.pipe(startWith(1))
.subscribe(() => this._updateErrorDisplay());
} else {
this._errorSubscription?.unsubscribe();
}
}
ngOnDestroy(): void {
this._errorSubscription?.unsubscribe();
}
private _updateErrorDisplay(): void {
this._renderer.setProperty(this._errorElement, 'innerText', null);
if (this._ngControl.errors) {
const firstErrorKey = Object.keys(this._ngControl.errors)[0];
const errorValue = <{ [key: string]: unknown }>this._ngControl.errors[firstErrorKey];
const errorMessage = fillInPlaceholders(validationErrorString[firstErrorKey], errorValue);
if (errorMessage) {
this._renderer.setProperty(this._errorElement, 'innerText', errorMessage);
}
}
}
}
In this moment it replace a ng-tamplate with a simple, but i want to use it