1
votes

I'm having a problem creating a custom validator to be used in a template driven form. I want to constructor inject the NgControl of the host element, which also has an NgModel directive. But I keep getting the following Angular compilation error:

ERROR in : Cannot instantiate cyclic dependency! NgModel ("
        <div>
                <label for="name">Your name</label>
                [ERROR ->]<input id="name" type="text" name="name" #name="ngModel" ngModel [requiredIfCondition]="toggleBool" r")

My directive class looks like this:

import { Directive, forwardRef, Injector, Input, Self } from '@angular/core';
import { AbstractControl, NG_VALIDATORS, NgControl, Validator } from '@angular/forms';

@Directive({
    selector: '[requiredIf][ngModel]',
    providers: [
        { provide: NG_VALIDATORS, useExisting: forwardRef(() => RequiredIfDirective), multi: true },
    ],
})
export class RequiredIfDirective implements Validator {
    private innerRequiredIfCondition: boolean = false;
    @Input()
    public set requiredIfCondition(val: boolean) {
        this.innerRequiredIfCondition = val;
        let hostControl: AbstractControl | null = this.ngControl.control;
        if (hostControl !== null) {
            hostControl.updateValueAndValidity();
        }
    }

    public constructor(@Self() private readonly ngControl: NgControl) {
    }

    public validate(c: AbstractControl): {[error: string]: string} | null {
        if (this.innerRequiredIfCondition && (c.value === null || c.value === '')) {
            return {
                requiredIf: 'In this context this field is required.',
            };
        }
        return null;
    }
}

I'm applying the directive like this:

<div>
    <label for="name">Your name</label>
    <input id="name" type="text" name="name" #name="ngModel" ngModel [requiredIfCondition]="toggleBool" requiredIf />
    <div *ngIf="name.errors && name.errors.requiredIf" class="red-text text-darken-3">
        Name is required now.
    </div>
</div>

I would try manually injecting the NgControl, but since it's an abstract class I think you can't do that anymore.

I have also tried injecting AbstractControl instead of NgControl, but the system can't find a provider for AbstractControl.

I can't seem to find any more information on this in the context of template driven forms, so I would really appreciate any ideas as to how to resolve this issue.

Thanks in advance, Joshua

1
By the way, replacing the application of ngModel by [(ngModel)]="SomeProperty" doesn't help ...Joshua Schroijen

1 Answers

2
votes

Didn't find the solution for this, but did find the workaround : through the injector.

public constructor(
  private injector: Injector
  @Self() private readonly ngControl: NgControl = this.injector.get(NgControl),
) {}

EDIT

Simply remove the provider from the directive itself, and provide it in the module instead.

Stackblitz