Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to implement Form Projection in Angular?

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?

like image 682
Richard Hunter Avatar asked Nov 25 '20 16:11

Richard Hunter


People also ask

What is projection in Angular?

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.

Which of the following are ways to build forms in Angular?

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.


1 Answers

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

like image 72
garbear Avatar answered Oct 19 '22 04:10

garbear