0

I am new to angular. We are trying to build a big form using Angular Reactive forms

PFA stackblitz link for the example. I have a parent component person-form and its two child component person-job and person-info , containing few fields in each.

I am having two difficulties which I am not able to achieve .

1) I want user to completely fill the parent-form and then submit at the end .But if I fill info details first and then job details form and if I went back to info ; the details in info are not there . How to keep the filled details in the forms using Reactive forms approach. This happens on child form navigation.

2) Secondly, I want to combine both the forms result into a single json and send it to parent form component i.e person-form . How to achieve this. I am now able to get

 {title: "XYZ", salary: "0000"} 
 // and
 {name: "ABC", age: "12"}

But I want something like this :

 { 
  combinedForm : {
     info:{name: "ABC", age: "12"},
     job:{title: "XYZ", salary: "0000"}
    }
 } 

 // OR

 {
  combinedForm : {
     name: "ABC", age: "12",title: "XYZ", salary: "0000"
   }
 }

So that I can send or receive data from server easily and in one go.

MyStackblitz

2 Answers2

0

You have a few options if you're deadset on using routing to achieved this.

before I go into it, you may find it easier to simply use a tab mechanism without routing.

The simplest solution here is a shared service that shares the form between components.

make a form service like:

@Injectable()
export class PersonFormService {
  private _form: FormGroup;
  get form() {
    return this._form;
  }
  constructor(private fb: FormBuilder) {
    this._form = this.fb.group({
      title: ['',Validators.required],
      salary:['',Validators.required],
      name: ['',Validators.required],
      age:['']});
  }
}

then provide this form service at your parent level:

@Component({
  selector: 'app-person-form',
  templateUrl: './person-form.component.html',
  styleUrls: ['./person-form.component.css'],
  providers: [PersonFormService]
})

then use the form in your components:

export class PersonJobComponent implements OnInit {

  constructor(private personFormService:PersonFormService) { }

  formJob:FormGroup;
  ngOnInit() {
    this.formJob= this.personFormService.form;
  }

  submit(){
    console.log(this.formJob.value);

  }
}

and

export class PersonInfoComponent implements OnInit {


  constructor(private personFormService: PersonFormService) { }

  form:FormGroup;
  ngOnInit() {
    this.form=this.personFormService.form;
  } 

}

Now you will have the entire form in both of your components.

working stackblitz: https://stackblitz.com/edit/angular-qrym5k

If you want to treat the two sub forms more separately for validation purposes or whatever, just restructure your form construction in the service into 2 form groups.

something like:

@Injectable()
export class PersonFormService {
  private _form: FormGroup;

  get combinedForm() {
    return this._form;
  }

  get jobForm() {
    return this._form.get('job') as FormGroup;
  }

  get infoForm() {
    return this._form.get('info') as FormGroup;
  }

  constructor(private fb: FormBuilder) {
    this._form = this.fb.group({
      info: this.fb.group({
        name: ['',Validators.required],
        age:['']
      }),
      job: this.fb.group({
        title: ['',Validators.required],
        salary:['',Validators.required]
      })
    });
  }
}

then just use personFormService.infoForm or .jobForm appropriately in your respective components.

bryan60
  • 28,215
  • 4
  • 48
  • 65
  • @ShubhamKaranjekar you should either choose an answer or explain why the provided solutions did not work – bryan60 May 27 '19 at 12:29
  • both the solutions works. I preferred your solution as it does not let user to restrict to proceed to next tab.So Thanks again – Shubham Karanjekar May 28 '19 at 07:06
  • @ShubhamKaranjekar I see you're new to SO, if a user provides you a solution that solves your issue, you should accept the answer as correct and upvote any answers you found helpful – bryan60 May 28 '19 at 16:26
0

You can use a service to share form data between two or more components. In your case your can use a service name PersonFormService. Service will be like this:

import { Injectable } from '@angular/core';

@Injectable()
export class PersonFormService {
  personForm: PersonForm;
  constructor() {
    this.personForm = new PersonForm();
  }

  set infoDetails(info) {
    this.personForm.info = info;
  }

  set jobDetails(job) {
    this.personForm.job = job;
  }

  get infoDetails() {
    return this.personForm.info;
  }

  get jobDetails() {
    return this.personForm.job;
  }
}

export class PersonForm {
  info: any;
  job: any;

  constructor() {
    this.job = {};
    this.info = {};
  }
}

Now in your both components you can use service like this:

person-job.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { Validators } from '@angular/forms';
import { PersonFormService } from '../../person-form.service';
@Component({
  selector: 'app-person-job',
  templateUrl: './person-job.component.html',
  styleUrls: ['./person-job.component.css']
})
export class PersonJobComponent implements OnInit {

  constructor(private fb: FormBuilder, private formService: PersonFormService) { }

  formJob:FormGroup;
  completeForm: any;
  ngOnInit() {
    this.formJob=this.fb.group({
      title: ['',Validators.required],
      salary:['',Validators.required]});

    this.formJob.patchValue(this.formService.jobDetails);
  }
  submit(){
    console.log(this.formJob.value);
    this.formService.jobDetails = this.formJob.value;
  }
}

person-info.component.ts

import { Component, OnInit } from '@angular/core';
import { FormBuilder } from '@angular/forms';
import { FormGroup } from '@angular/forms';
import { Validators } from '@angular/forms';
import { PersonFormService } from '../../person-form.service';
@Component({
  selector: 'app-person-info',
  templateUrl: './person-info.component.html',
  styleUrls: ['./person-info.component.css']
})
export class PersonInfoComponent implements OnInit {


  constructor(private fb: FormBuilder, private formService: PersonFormService) { }

  form:FormGroup;
  ngOnInit() {
    this.form=this.fb.group({
      name: ['',Validators.required],
      age:['']});

    this.form.patchValue(this.formService.infoDetails);
  }

  submitForm() {
    console.log("submit")
   this.formService.infoDetails = this.form.value; 
  } 

}

Now to address your second question you can use the spread operator to join your both forms into a single form. Your code will be like this:

this.completeForm = {...this.formService.infoDetails, ...this.formService.jobDetails }

See the complete stackblitz demo here

asimhashmi
  • 4,258
  • 1
  • 14
  • 34
  • what's the justification to do all of this vs just building the form in the service as in my answer? – bryan60 May 25 '19 at 14:42
  • @bryan60 I took the similar approach to share data among different components using service (a common approach). Just forgot that I could also put the form inside the service instead of putting it in components,so yeah your answer is better than mine :) – asimhashmi May 25 '19 at 15:05