Dealing with Undefined Issue in Angular Subscription Validation

Let me start by acknowledging that while there are numerous questions on this topic, none of them addressed the specific issue I encountered. Despite trying various approaches with maps and other methods, I am still unsure about the correct way to proceed.

My primary goal is to utilize this opportunity to edit the userProfile. The input fields should be preloaded with the existing values from the profile, ensuring that no validation errors occur.

Within the constructor() of my profile.component.ts, I invoke the following method:

readProfileData() {
 this.userService.getProfile()
   .pipe(first())
   .subscribe(
     userData => {
       this.firstname = userData.firstName;
       this.lastname = userData.lastName;
       this.username = userData.username;
       this.email = userData.email;
       this.dateOfBirth = userData.dateOfBirth;
       this.country = userData.location;
       this.profileImage = userData.profileImage;
       this.gender = userData.gender;
       this.lastUpdated = userData.lastUpdated;
       this.activated = userData.activated;
       this.userId = userData.userId;
       this.userService.profileImageUpdate$.next(this.profileImage);
     });
}

The getProfile() method is then invoked:

getProfile() {
    return this.http.get<any>(this.GET_PROFILE_API);
}

In my ngOnInit() method, I initiate the form building process:

public buildForm() {
 this.editForm = this.form.group({
   username: [this.username, [Validators.required, Validators.minLength(this.minLength), CustomValidators.validateCharacters], AlreadyTakenValidator.checkUsername(this.registrationService)],
   email: [this.email, [Validators.required, Validators.email, CustomValidators.validateCharacters], AlreadyTakenValidator.checkEmail(this.registrationService)],
   oldPassword: ['', [Validators.required]],
   newPassword: ['', [Validators.required]],
   newPasswordConf: ['', [Validators.required]],
   firstname: [this.firstname, [Validators.required, NoWhitespaceValidator()]],
   lastname: [this.lastname, [Validators.required, NoWhitespaceValidator()]],
   country: ['', [Validators.required]],
   dateOfBirth: ['', [Validators.required]],
   gender: ['', [Validators.required]],
 }
 , {
     validator: MustMatch('newPassword', 'newPasswordConf')
   })
}

The challenge at hand is that the input fields remain empty as the values are undefined. Manually entering values before applying the validators works, indicating a problem with the subscription. I need the variables to be populated in order for the fields to display the data.

I have explored solutions involving mapping to res.json or using toPromise, but nothing has proven effective so far. While I understand the asynchronous nature of the issue, I am uncertain how to address it without leaving the user stuck in a loading loop.

Answer №1

Let's entrust the task of creating the form to the service.

The service will:

  1. Retrieve the data from the API.
  2. Generate the form structure.
  3. Fill the form with the fetched data.
  4. Provide the completed form.

The component will simply utilize the form. To achieve this, follow the implementation steps for the service below:

import { Injectable } from '@angular/core';
import { map } from 'rxjs/operators';
import { FormBuilder, Validators } from '@angular/forms';
import { HttpClient } from '@angular/common/http';

@Injectable()
export class UserService {

  GET_PROFILE_API = 'https://jsonplaceholder.typicode.com/users/1';

  constructor(
    private readonly http: HttpClient,
    private readonly fb: FormBuilder
  ) { }

  getUserForm() {
    return this.fetchProfileData()
      .pipe(
        map(userProfile => this.generateUserForm(userProfile))
      );
  }

  private generateUserForm(user) {
    return this.fb.group({
      name: [user.name, Validators.required],
      username: [user.username, Validators.required],
      email: [user.email, Validators.required],
      phone: [user.phone, Validators.required],
      website: [user.website, Validators.required],
    });
  }

  private fetchProfileData() {
    return this.http.get<any>(this.GET_PROFILE_API);
  }

}

Your Component structure will be as follows:

Template:

<form 
  *ngIf="form$ | async as form" 
  [formGroup]="form">
  <div class="form-group">
    <label for="exampleInputEmail1">Name</label>
    <input type="text" formControlName="name" class="form-control" placeholder="Enter Name">
  </div>
  <div class="form-group">
    <label for="exampleInputEmail1">Username</label>
    <input type="text" formControlName="username" class="form-control" placeholder="Enter Username">
  </div>
  <div class="form-group">
    <label for="exampleInputEmail1">Email</label>
    <input type="text" formControlName="email" class="form-control" placeholder="Enter Email">
  </div>
  <div class="form-group">
    <label for="exampleInputEmail1">Phone</label>
    <input type="text" formControlName="phone" class="form-control" placeholder="Enter Phone">
  </div>
  <div class="form-group">
    <label for="exampleInputEmail1">Website</label>
    <input type="text" formControlName="website" class="form-control" placeholder="Enter Website">
  </div>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

Component Class:

import { Component } from '@angular/core';
import { FormGroup } from '@angular/forms';
import { Observable } from 'rxjs';
import { UserService } from './user.service';

@Component({
  selector: 'my-app',
  templateUrl: './app.component.html',
  styleUrls: [ './app.component.css' ]
})
export class AppComponent  {
  form$: Observable<FormGroup>;

  constructor(private userService: UserService) {}

  ngOnInit() {
    this.form$ = this.userService.getUserForm();
  }

}

For reference, here's a Functional Sample on StackBlitz.

Answer №2

Your situation is occurring because of the asynchronous nature of http.get. This means that by the time you receive values in your subscriber response, it has already called "buildForm()".

To resolve this issue, you can consider one of the following solutions:

a) Call "buildForm()" inside the subscription.

b) Use async-await in the method definition of "readProfileData()", include await at the function call of readProfileData(), and then call "buildForm()".

c) Convert these processes into promises and only resolve them when you have received your response.

For further information, you can refer to:

in-depth guide on async-await

handling promises

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

Testing components in Angular using Renderer2

Recently, I came across an Angular component with the following structure: constructor( @Inject(INJECTION_TOKEN_WINDOW) private window: Window, private renderer: Renderer2 ){} ngOnInit() { this.renderer.addClass(this.window.document.body ...

Using AngularFire and Firebase to convert observable arrays

My tech stack includes angular CLI, angularFire, and Firebase. I store my data in a real-time database. https://i.sstatic.net/fEbhN.png I retrieve data from Firebase using an observable: //Service.ts import { Injectable } from '@angular/core' ...

What are the steps to resolve TypeScript errors in OpenLayers version 6.6.1?

Since updating to OpenLayers 6.6.1, I have been bombarded with numerous typescript errors related to generics. For example... import olLayerVector from 'ol/layer/Vector'; import olFeature from 'ol/Feature'; public static highlightOver ...

Is it feasible to restrict generic classes for particular functions?

Imagine creating a customized container in TypeScript. Let's consider this straightforward example: class Container<T> { val: T; constructor(t: T) { this.val = t; } } Now, let's say you want to implement a function that can gene ...

Error: Attempting to access a property called 'sign' on an undefined value

I encountered an issue while signing my transaction where I received an error message stating sendTransaction needs signer. Even though both message (encrypted using keccak256) and signer have values, I am unsure why there is a problem when executing the w ...

I am interested in incorporating a personalized class into the Kendo UI for Angular TabStrip

<kendo-tabstrip (tabSelect)="onTabSelect($event)"> <kendo-tabstrip-tab title="Paris" [selected]="true"> <ng-template kendoTabContent> <div class="content"> ...

The specified type '{ file: ArrayBuffer; url: string; }' cannot be assigned to type '{ file: Blob; url: string; }'

This method is causing an error. Is there a way to fix it without changing the return type of the method? Are there any casts that can be applied to resolve the error? private downloadIt(url: string, applicationData: any, getRequest?: boolean): Observabl ...

significant issue arising from slow input box typing in angular 5 causing concern

We've encountered a troublesome issue that is hindering our progress despite completing a web app using angular 5 & template driven forms. Everything works flawlessly except for one feature causing major disruption, like a sniper shot hitting us unexp ...

A new feature reminiscent of grace has been introduced in the most recent release of ChartJs 2.9.3

Incorporating ng2-charts into my angular project for chart generation has been quite useful. It relies on ChartJs2.9.3. Within the project, I have utilized a shared ChartOptions for various dynamically generated charts with varying values. However, the cha ...

The benefits of utilizing switchMap for fetching route parameters in Angular 2

I am currently diving into the Angular Guide on Routing & Navigation. One interesting code snippet from the guide shows how to retrieve a parameter called 'id' from the router and use it with the service service to fetch a hero: ngOnInit() { ...

What are the steps to passing props to a dynamically imported React component?

I'm currently working on a project using Next.js, React, and TypeScript. One of the challenges I faced was dynamically importing a React component into my page while setting ssr to false, as the component contains references to window. These window r ...

Changing the button color dynamically in Angular Material

Currently, I am working on creating a sidebar menu with angular material. However, I am facing an issue in changing the button color based on some component property. I have gone through the documentation available at: https://material.angular.io/componen ...

The NativeScript WebSocket Module cannot be located: Issue: Unable to resolve 'nativescript-websockets'

I have developed an Angular web application that I now want to convert into an Android app using NativeScript. To enable the websocket functionality on Android, I have integrated nativescript-websockets library. tns plugin add nativescript-websockets Suc ...

Error in Typescript: The property 'a' is not defined in the type 'A'

Here is an example of the different types I am working with: type Place = { address: string } type Location = { latLng: string } type User = { name: string } & (Place | Location) When attempting to parse the data using this structure, I enco ...

The browser is unable to load the Angular app's component

I'm having trouble with my index.html and main.ts files. The browser just displays "loading..." and doesn't load the component I created. Can someone please assist me? <link rel="stylesheet" href="../node_modules/bootstrap/dist/css/bootstrap. ...

Error: Failed to fetch the data from the Firebase database

I have recently added an edit button to my product list, but when I click on it, the form page opens with no data populated. Upon debugging in my product.service.ts file, I noticed that it outputs null when using console.log(p). I believe this is where the ...

Issue encountered with ASP.Net Core on AWS Serverless: The middleware for the SPA default page was unable to locate and return the default page '/index.html'

Everything works flawlessly with my ASP.Net Core 6.0 application with Angular on Visual Studio, but once deployed to AWS Serverless and accessing '/', an error occurs. The default SPA page middleware cannot serve the default page '/index.h ...

The best approach for sending parameters to the parent class in TypeScript for optimal efficiency

What's the optimal solution to this problem? I really appreciate how we can specify attributes in the constructor and TypeScript takes care of handling everything to assign values to the props in JavaScript - like I did with 'department' her ...

Using Angular service worker to pre-fetch video files

Issue arises when the service worker prefetches the entire video embedded on the page, leading to performance problems. My ngsw-config.json only contains configurations for local files, whereas the video is located on a different subdomain under /sites/def ...

Error in Angular 2: The requested URL https://imgur-apiv3.p.mashape.com/ cannot be loaded. The use of a wildcard '*' is not allowed in the 'Access-Control-Allow-Origin' header

I'm attempting to upload an image using the imgur API at: . However, I keep encountering this error: XMLHttpRequest cannot load . A wildcard '*' cannot be used in the 'Access-Control-Allow-Origin' header when the credential ...