Establish a reactive form upon data completion (asynchronously) in Angular version 5

I've been encountering an issue with updating form values post fetching data from an API. I attempted to utilize the *ngIf technique, but unfortunately, the form remains invisible even though it is properly set.

Although I cannot provide the entire project, here's a glimpse into the component template and controller:

Template

<div class="page-title">
  <h3> Edit news </h3>
</div>
<div class="partner-add-form">
  <ng-container *ngIf='newsForm'>
    <form action="" [formGroup]='newsForm' (ngSubmit)="onSubmit()">
      <div class="row  ">
        <div class="input-field col s12 tooltip">
          <input formControlName='title' [id]="'title'" [type]="'text'">
          <label [for]="'title'">Title</label>
          <validator-errors [control]='newsForm.get("title")'></validator-errors>
        </div>
        <div class="input-field col s12 tooltip">
          <textarea class="materialize-textarea" formControlName='content' [id]="'content'"></textarea>
          <label [for]="'content'">Content</label>
          <validator-errors [control]='newsForm.get("content")'></validator-errors>
        </div>
        <div class="input-field col s12 margin-reset">
          <mat-form-field class="full-width">
            <mat-select [formControl]='newsForm.controls["partner_id"]'>
              <mat-option disabled selected>Categories</mat-option>
              <mat-option *ngFor="let partner of partners.data" [value]="partner.id">
              {{ partner.name }} </mat-option>
            </mat-select>
          </mat-form-field>
          <validator-errors [control]='newsForm.controls["partner_id"]'></validator-errors>
        </div>
        <div class="file-field col s12 input-field">
          <div class="btn">
            <span>File</span>
            <input (change)="fileChangeListener($event)" type="file"> </div>
          <div class="file-path-wrapper">
            <input class="file-path validate" type="text" placeholder="Upload one or more files"> </div>
        </div>
        <div class="col s12">
          <div class="flex flex-middle flex-center crop-area">
            <img-cropper #cropper [image]="data" [settings]="cropperSettings"></img-cropper>
            <i class="material-icons">arrow_forward</i>
            <div class="result rounded z-depth-1">
              <img [src]="data.image " *ngIf="data.image " [width]="cropperSettings.croppedWidth"
                [height]="cropperSettings.croppedHeight"> </div>
          </div>
        </div>
        <div class="col s12 form-bottom">
          <div class="left">
            <button type="button" onclick='window.history.back()' class='btn btn-large waves-effect waves-light '>
              <i class="material-icons">keyboard_arrow_left</i>
              <span>Back</span>
            </button>
          </div>
          <div class="right">
            <button [ngClass]="{'disabled':(newsForm['invalid']) || isSubmitting || !data.image }" type="submit" class='btn btn-large waves-effect waves-light '>
            Submit </button>
          </div>
        </div>
      </div>
    </form>
  </ng-container>
</div>

Controller

  partners;

  news = {};
  newsForm: FormGroup;

  ngOnInit() {
    setTimeout(() => {
      this._dashboardService.routeChangeStarted();
    }, 0);
    this._activatedRoute.params.subscribe(params => {
      this.news["id"] = params["id"];
      this.getPartners().then(data => {
        this.getNews().then(data=>{
          this.setForm();
        })
      });
    });
  }

  setForm() {
    this.newsForm = this._formBuilder.group({
     title: [this.news['title'], [Validators.required]],
     content: [this.news['content'], [Validators.required]],
     partner_id: [this.news['partner']['id'], [Validators.required]]
    });
    console.log(new Boolean(this.newsForm));
  }

  getPartners() {
    return Promise((res, rej) => {
      setTimeout(() => {
        this._dashboardService.progressStarted();
        this._dashboardService.routeChangeStarted();
      }, 0);
      this._partnerService.getPartners().subscribe(
        partners => {
          if (partners.status == 200) {
            this.partners = partners;
            res(partners.data);
          } else {
            this._errorActions.errorHandler(partners.status);
          }
          setTimeout(() => {
            this._dashboardService.progressFinished();
            this._dashboardService.routeChangeFinished();
          }, 0);
        },
        error => {
          this._notificationService.warning("Ups, something went wrong");
        }
      );
    });
  }

  getNews() {
    setTimeout(() => {
      this._dashboardService.routeChangeStarted();
      this._dashboardService.progressStarted();
    }, 0);

    return Promise((res, rej) => {
      this._newsService.getNews(this.news["id"]).subscribe(
        data => {
          if (data["status"] == 200) {
            Object.assign(this.news, data["data"]);
            res(this.news);
          } else {
            this._errorActions.errorHandler(data["status"]);
          }
          setTimeout(() => {
            this._dashboardService.progressFinished();
            this._dashboardService.routeChangeFinished();
          }, 0);
        },
        error => {
          this._notificationService.error("Ups, something went wrong");
        }
      );
    });
  }

The primary issue lies in the fact that the form does not display even after being correctly set. Are there alternate methods for setting form values following data retrieval from an API? Suggestions are greatly appreciated!

Answer №1

To populate your form with data, consider creating a separate function for this task. This function can be called after retrieving data from an API.

Method 1: setValue

Refer to the official Angular documentation: setValue

Using setValue allows you to set all form control values at once by providing a data object with properties that match the FormGroup's model.

Example:

updateValues(dataObject: any) {
  this.heroForm.setValue({
    name:    this.hero.name,
    address: this.hero.addresses[0] || new Address()
  });
}

Method 2: patchValue

Refer to the official Angular documentation: patchValue

Using patchValue allows you to assign values to specific controls in a FormGroup by providing key/value pairs for only the relevant controls.

Example:

updateValues(dataObject: any) {
  this.heroForm.patchValue({
    name: this.hero.name
  });
}

Answer №2

Dealing with a similar problem, I came up with a solution that seems to be working fine for me. The approach involves using a loaded: boolean variable that is initially set to false. Once all the necessary data is successfully received in your component, it should then be changed to true.

Here's an example implementation:

In HTML file:

<div *ngIf="loaded == false">
    <h2>loading ...</h2>
</div>
<div *ngIf="loaded == true">
   // Your template content goes here
</div>

And in your TypeScript file:

loaded: boolean = false;

// Your other code ....

ngOnInit() {
  setTimeout(() => {
    this._dashboardService.routeChangeStarted();
  }, 0);
  this._activatedRoute.params.subscribe(params => {
    this.news["id"] = params["id"];
    this.getPartners().then(data => {
      this.getNews().then(data=>{
        this.setForm();

        // This line marks the completion of data loading process
        this.loaded = true
      })
    });
  });
}

Answer №3

You have the option of using either setValue or patchValue to asynchronously populate data for your FormGroup.

private initializeForm(): void {
    // For updating a product
    if (this.isUpdate) {
      this.productService.getProduct(this.id)
        .subscribe((product: Product) => {
          this.productForm.patchValue({
            name: product.name,
            price: product.price,
            description: product.description,
            image: product.image
          });
        });
    }
    // For creating a new product
    this.productForm = new FormGroup({
      name: new FormControl(null, [
        Validators.required,
        Validators.minLength(8),
        Validators.maxLength(35)
      ]),
      price: new FormControl(null, [
        Validators.required,
        Validators.min(5000),
        Validators.max(50000000),
      ]),
      description: new FormControl(null, [
        Validators.required,
        Validators.minLength(8),
        Validators.maxLength(500)
      ]),
      image: new FormControl(null, Validators.required)
    });
  }

Answer №4

To ensure the form updates properly after a promise subscription, I had to trigger change detection manually. To do this, you can import

constructor(private cdr: ChangeDetectorRef) {
}

and then execute

this.cdr.detectChanges();

Similar questions

If you have not found the answer to your question or you are interested in this topic, then look at other similar questions below or use the search

Updating from Angular version 12.0.4 to 12.1.0 results in a runtime error. As a temporary solution, we are reverting back to version 12.0

There is a related issue discussed here: Angular: TypeError: Cannot read property 'firstCreatePass' of null However, the problem in that case pertains to different Angular versions and the solution provided did not resolve my issue. The recurring ...

Troubleshooting Vue 2 TypeScript Components Import Issue in VS Code

Has anyone experienced issues with TS pointing errors when importing custom components into a .vue file using the options api and webpack? The import is successful, everything works after bundling, but I'm still encountering annoying errors in the .vu ...

Tips for adding items to a Form Array in Angular

I am working on a project with dynamic checkboxes that retrieve data from an API. Below is the HTML file: <form [formGroup]="form" (ngSubmit)="submit()"> <label formArrayName="summons" *ngFor="let order of form.controls.summons.controls; let i ...

Group records in MongoDB by either (id1, id2) or (id2, id1)

Creating a messaging system with MongoDB, I have designed the message schema as follows: Message Schema: { senderId: ObjectId, receiverId: ObjectId createdAt: Date } My goal is to showcase all message exchanges between a user and other users ...

When utilizing Rx.Observable with the pausable feature, the subscribe function is not executed

Note: In my current project, I am utilizing TypeScript along with RxJS version 2.5.3. My objective is to track idle click times on a screen for a duration of 5 seconds. var noClickStream = Rx.Observable.fromEvent<MouseEvent>($window.document, &apos ...

An issue occurred at line 2, character 16 in the generateReport.service.ts file: TypeScript error TS2580 - The term 'require' is not recognized

I have a requirement in my app to generate a report in the form of a Word document with just a click of a button. Previously, I successfully accomplished this using the "officeGen" module in a separate project. You can find more information about the modul ...

Unable to sign out user from the server side using Next.js and Supabase

Is there a way to log out a user on the server side using Supabase as the authentication provider? I initially thought that simply calling this function would work: export const getServerSideProps: GetServerSideProps = withPageAuth({ redirectTo: &apos ...

In order to work with Mongoose and Typescript, it is necessary for me to

I am currently following the guidelines outlined in the Mongoose documentation to incorporate TypeScript support into my project: https://mongoosejs.com/docs/typescript.html. Within the documentation, there is an example provided as follows: import { Sche ...

Exploring Heroes in Angular 2: Retrieving Object Information by Clicking on <li> Items

Currently, I am delving into the documentation for an angular 4 project called "Tour of Heroes" which can be found at https://angular.io/docs/ts/latest/tutorial/toh-pt2.html. <li *ngFor="let hero of heroes" (click)="onSelect(hero)">{{hero.name}}< ...

Using Typescript with d3 Library in Power BI

Creating d3.axis() or any other d3 object in typescript for a Power BI custom visual and ensuring it displays on the screen - how can this be achieved? ...

Retrieve the TaskID of an ECS Fargate container for exporting and future use within AWS CDK code

Currently, I am leveraging CDK version 2 alongside Typescript. In my current setup, I encounter a situation where I necessitate the TaskID value from ECS Fargate Container to be incorporated into another command. The process involves me utilizing new ecs ...

What is the process for updating button text upon clicking in Angular?

To toggle the text based on whether this.isDisabled is set to false or true, you can implement a function that changes it when the button is clicked. I attempted to use this.btn.value, but encountered an error. import { Component } from '@angular/core ...

Why does using `withCredentials: true` and including a `body` in the request cause a CORS error in Angular HttpClient?

My objective is to make a request to a Cloud Function, receive a response with a Set-Cookie header, and have the browser store the cookie. The issue arises when the response containing a Set-Cookie header is ignored without the presence of withCredentials ...

The mat-menu generated with ngFor fails to activate the click function

I'm encountering difficulties when implementing a mat-menu using *ngfor I have consulted this response How can I utilize *ngFor with mat-menu and mat-menu-item? and although I believe I am following the same approach, I am still experiencing errors. ...

Streamline copyright verification with Angular

We are currently working on an angular application that we plan to release as open-source. We make sure to include copyright information in every file, specifically in the .ts and .scss files. However, being human, there are times when we may forget to ad ...

Creating a spy object in Jasmine for the forEach method of router.events

I have been attempting to create a test case for a component in an application and am having trouble with the constructor. Here is how it looks: constructor(private router: Router, public dialog: MatDialog, private tlsApiServi ...

Next.js Enhanced with Google Analytics

I've been experimenting with integrating Google Analytics into Next.js, following a tutorial on YouTube - https://www.youtube.com/watch?v=lMSBNBDjaH8 Following the instructions in the video, I added these two scripts in _document.js: <script async ...

Angular | The parameter type 'User[]' is incompatible with the expected parameter type 'Expected<User>' and cannot be assigned

While testing Angular, I encountered this error that has me stumped. The issue seems to be with the user inside the toBe() function. This error is occurring in the file user.service.spec.ts it('should call getUsersById', () => { const user ...

Error encountered in typescript when trying to implement the Material UI theme.palette.type

Just starting out with Material UI and TypeScript, any tips for a newcomer like me? P.S. I'm sorry if my question formatting isn't quite right, this is my first time on Stack Overflow. https://i.stack.imgur.com/CIOEl.jpg https://i.stack.imgur.co ...

I am completely baffled by the meaning of this Typescript error

In my code, there is a button component setup like this: export interface ButtonProps { kind?: 'normal' | 'flat' | 'primary'; negative?: boolean; size?: 'small' | 'big'; spinner?: boolean; ...