2

I'd like to transfer my ngForm and ngModelGroup from parent component to child component and also use form validation functionality in the child component.

Parent component.html:

<md-step [stepControl]="createIntegrationPartnerRequestGroup">
  <form #createIntegrationPartnerRequestForm="ngForm">
    <div ngModelGroup="createIntegrationPartnerRequestGroup" #createIntegrationPartnerRequestGroup="ngModelGroup">
      <div class="row p-row-padding">
        <div class="col-xs-12">
          <ng-template mdStepLabel>
            {{'INTEGRATION_PARTNER_REQUEST' | translate}}
          </ng-template>
        </div>
      </div>
      <integration-partner-request [form]="createIntegrationPartnerRequestForm"
                                   [model]="createIntegrationPartnerRequestGroup"></integration-partner-request>
    </div>
  </form>
</md-step>

Child Component.html (excerpt):

<div class="row p-row-padding">
  <div class="col-xs-6">
    <md-input-container class="p-full-width">
      <input mdInput
             ngModel name="name"
             #name="ngModel"
             required
             (change)="showForm()"
             placeholder="{{'WELCOME_WIZARD.PARTNER_NAME' | translate}}">
      <md-error *ngIf="name.touched && name.invalid">
        <span *ngIf="name.errors.required">
          {{'WELCOME_WIZARD.FIELD_REQUIRED' | translate}}
        </span>
        <span *ngIf="name.errors.pattern">
          {{'WELCOME_WIZARD.INVALID_FIELD_FORMAT' | translate}}
        </span>
      </md-error>
    </md-input-container>
  </div>
  <div class="col-xs-6">
    <md-input-container class="p-full-width">
      <input mdInput
             ngModel name="status"
             #status="ngModel"
             placeholder="{{'WELCOME_WIZARD.PARTNER_STATUS' | translate}}">
      <md-error *ngIf="status.touched && status.invalid">
        <span *ngIf="status.errors.required">
          {{'WELCOME_WIZARD.FIELD_REQUIRED' | translate}}
        </span>
        <span *ngIf="status.errors.pattern">
          {{'WELCOME_WIZARD.INVALID_FIELD_FORMAT' | translate}}
        </span>
      </md-error>
    </md-input-container>
  </div>
</div>

Child Component.ts:

export class ChildComponent implements OnInit {

  @Input() public form: FormGroup;
  @Input() public model: NgModelGroup;
  @Input() public type: string;

  public formResult;
  public modelResult;

  constructor() {
  }

  ngOnInit() {
    this.formResult = this.form;
    this.modelResult = this.model;
  }

  public showForm() {
    console.log(this.formResult);
    console.log(this.modelResult);
  }

}

There's also a button in the child component which should only work if the required field is not empty, but that is not the case, since the modelResult doesn't have any field values...:

<button
  class="p-full-width"
  md-raised-button
  [disabled]="formResult.invalid"
  mdStepperNext>
  {{'WELCOME_WIZARD.NEXT' | translate}}
</button>

What am I doing wrong?

Deniss M.
  • 3,617
  • 17
  • 52
  • 100

1 Answers1

2

You're doing everything correct - except that Angular 2/4 does not support it. If you look at the source code for NgModel you'll see that it contains such definition:

constructor(@Optional() @Host() parent: ControlContainer,
          ...)

This means that DI container will only look for this dependency up to first @Host() which would be - guess what? - your child component. This means that NgModel will not find your form and will not register itself with it. Every @Component() is a host, so there's no way to make it work like this.

So, your choice is really limited here: gather all the form parts in a single component or build your own NgModel implementation.

As @yurzui pointed out (thanks!) there is a solution: click. I haven't tried it myself but I don't see why it wouldn't work - and it's pretty simple and elegant.

Alexander Leonov
  • 4,694
  • 1
  • 17
  • 25
  • I think I actually found a solution to my problem. I don't know how correct it is, but here it goes. I have one `
    ` in my parent and one `
    ` in my child component. I am using two way binding to pass the form from the child component to the parent component. I am not sure if this will work and if I can substitute forms like this. Any comments?
    – Deniss M. Oct 16 '17 at 18:51
  • 5
    `so there's no way to make it work like this.` I know one way to do it working https://medium.com/@a.yurich.zuev/angular-nested-template-driven-form-4a3de2042475 See also https://stackoverflow.com/questions/39242219/angular2-nested-template-driven-form/46748943#46748943 – yurzui Oct 16 '17 at 18:53
  • That's actually an interesting solution - but it is still some kind of workaround, though I admit it's pretty elegant. Thanks for pointing out. I'll add it to answer. – Alexander Leonov Oct 16 '17 at 19:07
  • @yurzui thanks! This looks interesting, is it two way binded? – Deniss M. Oct 16 '17 at 19:10
  • 1
    @yurzui It works in the same way as if you place all the form parts in a single template – yurzui Oct 16 '17 at 19:13
  • @yurzui do I need to have the second ngModelGroup inside the child component or can I go without it? – Deniss M. Oct 16 '17 at 19:17
  • @DenissM. I depents on what you are expecting. You can use `useExising: NgModelGroup` if you want that your child component will be a part of parent ngModelGroup – yurzui Oct 16 '17 at 19:23
  • @yurzui I have all the fields of a form inside a child component and I need to access the form.valid or invalid state through those fields inside the parent component. – Deniss M. Oct 16 '17 at 19:25
  • 1
    @DenissM. Just use `form.valid` since you do not need to have another form inside child component, all children will be part of parent ngForm – yurzui Oct 16 '17 at 19:28
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/156857/discussion-between-deniss-m-and-yurzui). – Deniss M. Oct 16 '17 at 19:28