Raj
Raj

Reputation: 1120

How to work with Dynamic Forms (get values from api for update form) in Angular?

I have a Component:component, where i need to update a form. On init, i am dispatching action to load the detail, and subscribed to success-action, where i will create formControls. I am getting the data from store, but still i always get this error, in all the forms:

Cannot read property 'name' of undefined

This is how the form looks when there is the above error, screenshot. And this is how the form should be looking, screenshot.

I tried for many hours but could not figure wIere i was going wrong.

component.ts

formGroup: FormGroup;

  ngOnInit() {
    this.store.dispatch(new fromCourse.LoadCardDetails());

    this.actionsSubject$
        .pipe(filter(action => action.type === CourseActionTypes.LOAD_CARD_DETAILS_SUCCESS))
        .subscribe((data: {type, payload: ICardContent } ) => {
          this.formGroup = new FormGroup({
            name: new FormControl(`${data.payload.name}`, []),
            content_type: new FormControl(`${data.payload.content_type}`),
            actual_content: new FormControl(`${data.payload.actual_content}`),
            content_url: new FormControl(`${data.payload.content_url}`),
            additional_content: new FormControl(`${data.payload.additional_content}`),
            media_type: new FormControl(`${data.payload.media_type}`),
            media_source: new FormControl(`${data.payload.media_source}`),
            language_id: new FormControl(`${data.payload.language_id}`),
            thumbnail: new FormControl(`${data.payload.thumbnail}`),
          });
        });
  }

  get content_type () { return this.formGroup.get('content_type'); }
  get name () { return this.formGroup.get('name'); }
  get actual_content () { return this.formGroup.get('actual_content'); }
  get content_url () { return this.formGroup.get('content_url'); }
  get additional_content () { return this.formGroup.get('additional_content'); }
  get media_type () { return this.formGroup.get('media_type'); }
  get media_source () { return this.formGroup.get('media_source'); }
  get language_id () { return this.formGroup.get('language_id'); }
  get thumbnail () { return this.formGroup.get('thumbnail'); }

component.html

<form novalidate class="mainForm" [style.fontSize.px]="15" [formGroup]="formGroup">

    <div>
    <h3 class="sHeading"> Form Type </h3>
    <mat-form-field appearance="fill" class="formField">
        <mat-select placeholder="Select Type" formControlName="content_type">
            <mat-option 
                #formType
                class="option" 
                *ngFor="let type of types" 
                [value]="type"
                (click)="updateFormType(formType.value)">
            {{type}}
            </mat-option>
        </mat-select>
    </mat-form-field>
    </div>

    <div>
    <h3 class="sHeading"> Name </h3>
    <mat-form-field appearance="outline" class="formField">
        <input matInput 
            placeholder="Name should have 3 characters min." 
            formControlName="name">
    </mat-form-field>
    </div>

    <div>
    <h3 class="sHeading"> Content </h3>
    <mat-form-field appearance="outline" class="formField">
        <textarea 
            matInput 
            placeholder="Describe the content"
            rows=6
            formControlName="actual_content"></textarea>
    </mat-form-field>
    </div>

    <div class="button-container">
        <button mat-raised-button type="submit" class="submitBtn" (click)="onSubmit(formGroup.value)">Update Form</button>            
    </div>

</form>

Thanks in advance.

Upvotes: 0

Views: 1499

Answers (3)

SiddAjmera
SiddAjmera

Reputation: 39432

I'm sure it's because you're adding getters to your code before even setting the this.formGroup. These getters will not wait for your formGroup to be initialized and will call methods on them which would end up giving you errors on the console.

You should set the form in ngOnInit and then call the patchValue method on it to set the value of the form with the data that you're getting from your store.

formGroup: FormGroup;

ngOnInit() {

  this.formGroup = new FormGroup({
    name: new FormControl(),
    content_type: new FormControl(),
    actual_content: new FormControl(),
    content_url: new FormControl(),
    additional_content: new FormControl(),
    media_type: new FormControl(),
    media_source: new FormControl(),
    language_id: new FormControl(),
    thumbnail: new FormControl(),
  });

  this.store.dispatch(new fromCourse.LoadCardDetails());

  this.actionsSubject$
    .pipe(filter(action => action.type === CourseActionTypes.LOAD_CARD_DETAILS_SUCCESS))
    .subscribe((data: {
      type,
      payload: ICardContent
    }) => {
      this.formGroup.patchValue(data.payload);
    });
}

get content_type() {
  return this.formGroup.get('content_type');
}
get name() {
  return this.formGroup.get('name');
}
get actual_content() {
  return this.formGroup.get('actual_content');
}
get content_url() {
  return this.formGroup.get('content_url');
}
get additional_content() {
  return this.formGroup.get('additional_content');
}
get media_type() {
  return this.formGroup.get('media_type');
}
get media_source() {
  return this.formGroup.get('media_source');
}
get language_id() {
  return this.formGroup.get('language_id');
}
get thumbnail() {
  return this.formGroup.get('thumbnail');
}

Upvotes: 1

T. van den Berg
T. van den Berg

Reputation: 364

The problem is that your view needs the formGroup to render the html but that is null by default and only defined in the subscriber.

You better move the creation of the form to the constructor (use empty strings by default).

     constructor(){
        this.formGroup = new FormGroup({
             name: new FormControl('', []),
             content_type: new FormControl(''),
             actual_content: new FormControl(''),
             content_url: new FormControl(''),
             additional_content: new 
                   FormControl(''),
             media_type: new FormControl(''),
             media_source: new FormControl(''),
             language_id: new FormControl(''),
             thumbnail: new FormControl(''),
      });

Then when the subscription is triggered patch the existing form with new data:

this.formGroup.get('content_type').patchValue(data.payload.content_type);

In this way the form always exists.

Upvotes: 0

Suresh Kumar Ariya
Suresh Kumar Ariya

Reputation: 9764

Might be, Form is getting loaded before the API call is getting completed. Create a Flag and set to true once we receive API response.

In Template, you can handle using *ngIf.

<div *ngIf="dataLoaded"></div>

Component:

dataLoaded = false;
ngOnInit() {
    this.store.dispatch(new fromCourse.LoadCardDetails());

    this.actionsSubject$
        .pipe(filter(action => action.type === CourseActionTypes.LOAD_CARD_DETAILS_SUCCESS))
        .subscribe((data: {type, payload: ICardContent } ) => {
          this.dataLoaded = true;
          this.formGroup = new FormGroup({
            name: new FormControl(`${data.payload.name}`, []),
            content_type: new FormControl(`${data.payload.content_type}`),
            actual_content: new FormControl(`${data.payload.actual_content}`),
            content_url: new FormControl(`${data.payload.content_url}`),
            additional_content: new FormControl(`${data.payload.additional_content}`),
            media_type: new FormControl(`${data.payload.media_type}`),
            media_source: new FormControl(`${data.payload.media_source}`),
            language_id: new FormControl(`${data.payload.language_id}`),
            thumbnail: new FormControl(`${data.payload.thumbnail}`),
          });
        });
  }

Upvotes: 1

Related Questions