In my app I introduced a mapper directive responsible for populating form values from a class. Intended to be used as in:
<form [formGroup]="form" [myItemMapper]="item$ | async">
Not intended to be on any other element, such as form array or form control.
To achieve this intention, I restricted the selector to be [formGroup][myItemMapper], which works, ie. if you place this directive on any other element, you get a big Webpack compilation overlay with the error saying
`NG8002: Can't bind to 'myItemMapper' since it isn't a known property of '<whatever>'.
Goal reached.
But I was wondering why the following code didn't work:
constructor(
@Self() fg: FormGroupDirective,
) {
if(!fg){
console.error("'myItemMapper' can only be used on a FormGroup directive.")
}
}
- instead of a nice informative
console.error, I got a nasty "No provider for formGroupDirective found" runtime error with no explanation. For a consumer it is a misleading experience as if they didn't importReactiveFormsModuleat all.
Tried @Host() too - it basically didn't care which element the directive was on, it was able to find formGroup also when it was placed on a formControlName- which is also something I don't understand.
So - is the first option the best I can get for my directive's consumer?
CodePudding user response:
But I was wondering why the following code didn't work:
instead of a nice informative console.error, I got a nasty "No provider for formGroupDirective found" runtime error with no explanation. For a consumer it is a misleading experience as if they didn't import ReactiveFormsModule at all.
When the dependency cannot be resolved, Angular will throw an error "No provider for {token} found". That's an expected behavior.
In case if you would like to get the console.error message to be printed rather the error being thrown, you should also make use of @Optional() decorator as:
constructor(
@Self() @Optional() fg: FormGroupDirective,
) {
if(!fg){
console.error("'myItemMapper' can only be used on a FormGroup directive.")
}
}
From docs
@Optional() allows Angular to consider a service you inject to be optional. This way, if it can't be resolved at runtime, Angular resolves the service as null, rather than throwing an error.
Now coming to the next question:
Tried @Host() too - it basically didn't care which element the directive was on, it was able to find formGroup also when it was placed on a formControlName- which is also something I don't understand.
When using @Host(), Angular will start dependency resolution from the current Injector and will continue to search in the Injector hierarchy until it reaches the host element of the current component.
Let's say you have below template for your SomeComponent:
<form [formGroup]="form">
<label for="firstName">First Name</label>
<input id="firstName" type="text" formControlName="firstName" yourDirective />
</form>
Then using @Host() within your directive constructor, Angular will try to resolve dependency as follows:
- providers array of
yourDirectivefollowed by that ofFormControlNamedirective - providers array of
FormGroupDirective - viewProviders array of
SomeComponent
CodePudding user response:
When adding [formGroup][myItemMapper] as the selector for the directive, you are telling the compiler that the MyItemMapperDirective must be used with a FormGroupDirective. This can be enforced at compile time.
By adding FormGroupDirective as a constructor parameter, you are asking the Dependency Injection container to find it as a dependency. Since dependencies can be affected by other components at run time, whether or not the required dependency exists can only be determined at run time, hence the error message you are seeing.
So yes, the first option is your best bet.
