elements to display validation messages for the form" /> elements to display validation messages for the form" /> elements to display validation messages for the form"/>

Show <ng-template> where it is located in angular template using a directive

45 Views Asked by At

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:

  1. 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.

  2. If no errorTemplateRef is 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

0

There are 0 best solutions below