Angular Dependency Injection use-case in Forms
I’m an Angular developer who wants to share own experience about Dependency Injection (DI) use-case when working with Angular Forms.
First of all small prehistory. I’m working on a project where is lots of forms and they require validation as well as showing error messages after form submit. Since I’m a programmer I’ve created a dedicated component which creates different input types based on passed type as well as shows errors when they appear.
Because of the limitation that user should see the errors only after the form is submitted as well as see the progress of correcting these field errors I needed a solution to tell my component that the form was submitted and it’s time to start annoying user about wrong fields.
First and simple solution, though is more like hard-coding is to simply pass submitted
property deep down in the component tree what requires on each level writing of something like <my-component [submitted]="submitted'></my-component>
until I reach the bottom where my dedicated component is placed.
Pros:
- Simple implementation
- First comes to mind since we often use
@Input()
decorators to pass whatever we want
Cons:
- Each level requires own
@Input()
decorator to get the value - The
submitted
property may be changed by mistake somewhere in “buffer” layers - Extra code in each component to simply pass one value (imagine what would be if there is more than 5 components between form host and the dedicated component)
Second and not so obvious solution is to use DI. According to Angular docs we can use DI to navigate towards the top of the component tree. Since we would use either Reactive Forms or Forms we will use [formGroup]
and #heroForm=”ngForm”
respectively.
In Angular there are FormGroupDirective and NgForm which has desired property submitted
. To get this we would need to write following
@Component({...})
export class InputComponent implements OnInit {
... submitted = false; constructor(
@Optional() private formGroupDirective: FormGroupDirective
) { } ngOnInit() {
/**
* If formGroupDirective is found then subscribe to submit
*/ if (this.formGroupDirective) {
this.formGroupDirective.ngSubmit.subscribe(() => {
this.submitted = true;
});
}
}
Where we use @Optional()
decorator which results in null
if there is no such directive instead of error. Then, we subscribe on `ngSubmit` which is an EventEmitter and fired when “submit” is pressed.
If there is a need to work with Template Driven Forms one may use NgForm
instead of FormGroupDirective
which is very similar.
Pros:
- No need in extra parameter to pass
submitted
state - It’s much safer to use since
submitted
wouldn’t be changed by mistake while passing through layers of components - Code looks cleaner, therefore more readable and easier to maintain
Cons:
- If we add somewhere in-between
[formGroup]
and input-component one more[formGroup]
DI would stop on that which is closer to input-component what means thatngSubmit
won’t be fired when submit happens (because it will happen to parent). - Requires
@Optional()
decorator for safe use and then check if it’s not null (it will work if we skip this decorator, however this decorator will save us from possible errors in code if one would place input-component outside offormGroup
)
The working example with second case can be tested here