Angular: Nested template driven form
In this article i am going to show how we can build nested angular template drived form.
I am assuming you have already known what ngForm
, ngModel
, ngModelGroup
directives are for.
So let’s start with simple form:
There is nothing new here for those how do it every day. We’ve written form containing firstName
and lastName
fields and we’re expecting the result like:
{
"firstName": "",
"lastName": ""
}
Now let’s imagine our client has changed requirements and we have to add address fields to our form:
and the value will look as follows:
{
"firstName": "",
"lastName": "",
"address": {
"zip": "",
"street": "",
"city": ""
}
}
As the app grows inevitably you will face the situation when you need to reuse address fields from the preceding template.
That’s time to refactor our code. For that we can create AddressComponent
and use it in parent component:
The problem
Unfortunately, as soon as we do it, we’ll get the error:
Template parse errors: No provider for ControlContainer (“[ERROR ->]<fieldset ngModelGroup=”address”>
Hmm… Angular complains about ControlContainer
provider for ngModelGroup
directive.
Let’s try to find solution
— Where does ControlContainer
come from?
If we take a look at source code we can notice that NgForm
and NgModelGroup
directives provide it within their providers
array.
Here is excerpt from ng_form.ts
:
This way angular can’t find ControlContainer
provider that is declared on form
element(NgForm
directive lives there).
Now let’s open source code to see where angular requires it.
As we can see angular blocks search for ControlContainer
provider by using Host decorator.
The documentation states that Host decorator:
Specifies that an injector should retrieve a dependency from any injector until reaching the host element of the current component.
Let’s get back to our AddressComponent
and take look at its template:
In order to find provider angular will walk up from fieldset
until it reaches address
host element.
But our parent ngForm
is located within parent template while NgModelGroup
directive requires parent element within current template something like:
<some-element ngForm> <some-element ngModelGroup>
The most straightforward solution would be passing parent FormGroup
to child component. Such approach is typical for model driven form but we can’t pass FormGroup
to ngForm
directive.
Let’s get back to the error:
No provider… No provider… No provider…
Seems angular wants us to declare ControlContainer
provider. So let’s do it!
For those who know how angular DI works it should be easy. Host decorator gives us the opportunity to get a provider from viewProviders
declared for host element:
That’s all.
Working example could be found on Stackblitz