1

I have an angular 6 app. And I have a form like below stackblitz.

PERFECTLY WORKING SAMPLE

But, I want create a shared country-state component. And I want to call when I need. So, I convert my project into below stackblitz. But, I don't want create new form group for country-state. I want to use from parent . But, I can't execute my second stackblitz.

NOT WORKING SAMPLE

How can I achieve this?

realist
  • 2,155
  • 12
  • 38
  • 77
  • 1
    Why you do not pass the FormGroup as an Input to the child component ? – SeleM Dec 24 '18 at 14:20
  • I'm new at angular. How can I do that? @selemmn – realist Dec 24 '18 at 14:23
  • It's quite contradictory to extract the country and the state to a separate component, but to force each user of that component to create the form controls for that separate component instead of letting it create its own form controls. Why don't you want to create a separate form group? – JB Nizet Dec 24 '18 at 14:26
  • Because I'm using a lot of page @JBNizet. And I don't want write everywhere same code. – realist Dec 24 '18 at 14:49
  • That's precisely why you should not force every usage of the separate component to create the controls, and instead let the separate component do it. So why don't you want to create a separate form group for this separate component? – JB Nizet Dec 24 '18 at 15:54
  • Because there will be two recursive formgroup. And getting and managing values is more difficult. So, I prefered this way @JBNizet – realist Dec 24 '18 at 17:42
  • that could also help: https://stackoverflow.com/questions/55334283/reactive-forms-how-to-add-new-formgroup-or-formarray-into-an-existing-formgroup –  Mar 25 '19 at 12:02

3 Answers3

2

That's a main purpose of @Input() decorator in angular:

In your country-state.component.ts add Input to your import and use that decorator as mentioned below:

import { Component, OnInit, Input } from '@angular/core';
...

@Component({
  selector: 'app-country-state',
  templateUrl: './country-state.component.html',
  styleUrls: ['./country-state.component.css']
})
export class CountryStateComponent implements OnInit {

  countries: Country[];
  states: State[];

  @Input() 
  studentForm; // <-- This
...
}

Then in your app.component.ts change your child tag to:

<app-country-state [studentForm]="studentForm"></app-country-state>

After that and in your country-state.component.html add form tag like :

<form [formGroup]="studentForm">
    <label>Country:</label> 
    <select formControlName="countryId" (change)="onSelect($event.target.value)">
      <option [value]="0">--Select--</option>
      <option *ngFor="let country of countries" [value]="country.id">{{country.name}}</option>
    </select>
    <br/><br/>

    <label>State:</label>
    <select formControlName="stateId">
      <option [value]="0">--Select--</option>
      <option *ngFor="let state of states " value= {{state.id}}>{{state.name}}</option>
    </select>
 </form>

Thus, you wont create a new formgroup instance but you'll be using the parent's one.

SeleM
  • 9,310
  • 5
  • 32
  • 51
  • @HasanOzdemir, could you tell me please why did you accept the other answer ? it was a bit copy/paste from mine ? – SeleM Dec 24 '18 at 14:55
  • 1
    Sory. I saw that working in stackblitz. So, I marked. But, you are right. I'm changing immediately. – realist Dec 24 '18 at 15:07
2

Try composite form using controlContainer

parent.component.ts

this.studentForm = new FormGroup({
      'studentName': new FormControl(''),
      'stateId': new FormControl(''),
      'countryId': new FormControl('')
    })

parent.component.html

<form [formGroup]="studentForm" (ngSubmit)="onSubmit()">
    <label>Student Name:</label> 
    <input formControlName="studentName">
    <br/><br/>
    <app-country-state></app-country-state>
    <br/><br/>
    <button type="submit">SAVE</button>
</form>

Inside child component provide ViewContainer to get parent group control

import { Component, OnInit } from '@angular/core';
import { Country } from '../country';
import { State } from '../state';
import { SelectService } from '../select.service';
import { FormControl, FormGroupDirective, FormGroup, ControlContainer } from '@angular/forms';

@Component({
  selector: 'app-country-state',
  templateUrl: './country-state.component.html',
  styleUrls: ['./country-state.component.css'],
  viewProviders: [{ provide: ControlContainer, useExisting: FormGroupDirective }]
})
export class CountryStateComponent implements OnInit {
  countryId;

  countryState: FormGroup;
  countries: Country[];
  states: State[];

  constructor(private selectService: SelectService, private parentcontrol: FormGroupDirective) { }

  ngOnInit() {

    this.countryState = this.parentcontrol.control;

    this.parentcontrol.control.updateValueAndValidity();
    //  this.parentcontrol.control.addControl('stateId', new FormControl(''));
    this.countries = this.selectService.getCountries();
  }


  onSelect(countryid) {
    // this.states = this.selectService.getStates().filter((item) => item.countryid == this.studentForm.controls.countryId.value);
  }

}

Example: https://stackblitz.com/edit/angular-guqxbh

Chellappan வ
  • 23,645
  • 3
  • 29
  • 60
  • Thanks for your reply @Chellappan – realist Dec 24 '18 at 15:08
  • But your stackblitz example not working. Please can you edit it @Chellappan . – realist Dec 24 '18 at 18:56
  • 1
    Thanks for your reply. I edited `onChange` event. People who need full working version access from https://stackblitz.com/edit/angular-tfoiav?file=src/app/country-state/country-state.component.ts – realist Dec 25 '18 at 13:36
1

you need to pass your formGroup to child component as an input

country-state.component.ts

@Input() studentForm;

in app.component.html

<app-country-state [studentForm]="studentForm"></app-country-state>

in country-state.component.html

<form [formGroup]="studentForm" >
   ....
  </form>

I have modified your code https://stackblitz.com/edit/angular-q3pqgk

Indrakumara
  • 1,595
  • 17
  • 22