I have been trying to implement form projection in Angular in accordance with the talk given by Kara Erickson at Angular Connect in 2017, but so far without success. link to talk
Unfortunately, the only code available appears in the slides and is incomplete so working it out is difficult. This is what I have attempted but it is throwing errors: https://stackblitz.com/edit/angular-form-projection-3
The idea of form projection is that you have some wrapper component whose template contains a <form>
element and into which you project the form contents (the example in the talk is of a form stepper):
<!-- FormStepper -->
<form>
<ng-content></ng-content>
</form>
<!-- containing template, e.g. AppComponent -->
<form-stepper>
<div ngModelGroup="address">
<input name="street" ngModel/>
<input name="city" ngModel/>
</div>
</form-stepper>
A naive implementation such as this results in errors because the ngModelGroup directive expects to be within a form but cannot find the form directive (The ControlContainer class) because of the component boundary.
According to Kara, the solution is to provide the ControlContainer class from the FormStepperComponent's providers. Using a @ViewChild() decorator query, you grab the form directive from the component's view and store it in an instance property, e.g. form
.
// within FormStepper
@ViewChild(NgForm) form: NgForm;
Then within the providers array, you configure a provider for ControlContainer using useFactory so that when it is requested, it returns the form instance:
providers: [
{
provide: ControlContainer,
useFactory: component => {
return component.form;
},
deps: [FormStepperComponent]
}
],
Directives within the FormStepperComponent's content children should be able to inject services provided in the provider's array. This should allow ngModelGroup to get the reference to the form that it needs.
The problem is that the factory function runs before the view is initialised, therefore component.form is undefined at this point.
I have also created this demo which demonstrates content projection and the order in which the view is initialised, the content is initialised, and the factory runs.
https://stackblitz.com/edit/angular-projection-experiment
You can see in the console that the factory provider runs before the view is initialised and so when the directive tries to inject the form it just gets undefined
.
It looks like what is suggested in this talk isn't even possible?
Content projection is a pattern in which you insert, or project, the content you want to use inside another component. For example, you could have a Card component that accepts content provided by another component.
Angular supports two design approaches for interactive forms. You can build forms by using Angular template syntax and directives to write templates with the form-specific directives.
The reason your factory doesn't have the NgForm
view child defined is that the NgForm ViewChild
query will query after change detection runs by default. You need to update the NgForm ViewChild
query to run before change detection runs. This will make sure that the form
property is defined when your factory runs. You can do this by adding the property static
to the query and setting it to true, i.e.,
@ViewChild(NgForm, { static: true }) form: NgForm;
Docs on ViewChild go more in depth on this option: https://angular.io/api/core/ViewChild
I believe when this talk was given, Angular's ViewChild would query before change detection runs by default. Angular has since been updated to query after change detection runs by default. It's too bad they didn't provide code examples for this talk and keep it updated with latest Angular.
I forked your example to make it work: https://stackblitz.com/edit/angular-projection-experiment-uxzdee
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With