What could be causing the peculiar behavior I am experiencing when a child component attempts to display values from an object fetched in the parent component?

I am currently developing an Angular application and encountering a challenge when it comes to passing data from a parent component to a child component using the @Input decorator

The parent component is named PatientDetailsComponent. Here is the TypeScript code for this component:

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { SelectItem } from 'primeng/api';
import { Observable } from 'rxjs';
import { PatientService } from 'src/app/services/patient.service';
import { Patient } from 'src/app/shared/interfaces/patient';

@Component({
  selector: 'app-patient-details',
  templateUrl: './patient-details.component.html',
  styleUrls: ['./patient-details.component.scss']
})
export class PatientDetailsComponent implements OnInit, OnDestroy {

  patientUID: string;
  private sub: any;

  public patient: Patient;

  public patient$: Observable<Patient>;

  editPatientOption: SelectItem[];
  editPatientSelectedOption: string = "info";
  selectedEditPatientId: string;

  constructor(
              private route: ActivatedRoute,
              private patientService: PatientService
             ) { }

  ngOnInit(): void {
    this.sub = this.route.params.subscribe(params => {
      this.patientUID = params['id']; 

      this.editPatientOption = [{label: 'Info', value: 'info'}, {label: 'Edit', value: 'edit'}];

      console.log("RETRIEVED PATIENT UID ENTERING IN PatientDetailsComponent: " + this.patientUID);

      
      this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
        console.log("RETRIEVED PATIENT: ", patient);
        this.patient = patient;
      });
   });
  }

  ngOnDestroy() {
    this.sub.unsubscribe();
  }


  editPatientOptionOnChange(event, patientId) {
    console.log("editPatientOptionOnChange START, event: ", event);
    console.log("Patient ID: ", patientId);
    this.selectedEditPatientId = patientId;
  }


}

Within the ngOnInit() method of the parent component, there's a call to a service method that fetches patient information from Firestore DB. This line retrieves the patient object from the database:

  this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
    console.log("RETRIEVED PATIENT: ", patient);
    this.patient = patient;
  });

The retrieved patient object needs to be passed on to the child components.

This is how the HTML code of my parent component looks like:

<div class="container">  

    <p-selectButton [options]="editPatientOption"
                    [(ngModel)]="editPatientSelectedOption"
                    (onChange)="editPatientOptionOnChange($event, 5)"></p-selectButton>

    
    <div *ngIf="editPatientSelectedOption=='info';then info_content else edit_content">here is ignored</div>

    <ng-template #info_content>
        <app-patient-details-info [patientDetails]="patient"></app-patient-details-info>
    </ng-template>

    <ng-template #edit_content>
        <app-patient-details-edit [patientDetails]="patient"></app-patient-details-edit>
    </ng-template>

</div>

Depending on the selection made by the p-selectButton, either one of the two child components will be rendered. The patient object is passed as an @Input to these child components.

For example, here's the TypeScript code for the first child component:

import { AfterViewInit, Component, Input, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { Patient } from 'src/app/shared/interfaces/patient';

@Component({
  selector: 'app-patient-details-info',
  templateUrl: './patient-details-info.component.html',
  styleUrls: ['./patient-details-info.component.scss']
})
export class PatientDetailsInfoComponent implements OnInit, AfterViewInit  {

  @Input()
  patientDetails: any

  patientDetail: Patient;
  disabled = true;

  constructor() { }
  

  ngOnInit(): void {

    console.log("PATIENT DETAILS: ", this.patientDetails);

  }

  ngAfterViewInit(): void {
    console.log("ngAfterViewInit() START !!!");


  }

}

In order to access the object passed down from the parent component, this line is used:

@Input()
patientDetails: any

The HTML code for this child component includes:

<div class="row">
    <div class="col-2">
      <p>ID Ordine</p>
    </div>
    <div class="col-10">
      <input id="disabled-input" type="text" pInputText [disabled]="disabled" [(ngModel)]="patientDetails.completeName" />
    </div>
  </div>

An input tag is declared, grabbing the value from the model object passed from the parent component to this child component.

However, I am facing issues where Chrome console displays errors such as:

core.js:4442 ERROR TypeError: Cannot read property 'completeName' of undefined
    at PatientDetailsInfoComponent_Template (patient-details-info.component.html:17)
    at executeTemplate (core.js:7457)
    at refreshView (core.js:7326)
    at refreshComponent (core.js:8473)
    at refreshChildComponents (core.js:7132)
    at refreshView (core.js:7376)
    at refreshEmbeddedViews (core.js:8427)
    at refreshView (core.js:7350)
    at refreshComponent (core.js:8473)
    at refreshChildComponents (core.js:7132)

After these errors, the output of the console.log() defined inside the child component's ngOnInit() method displaying the retrieved object:

{id: "RmGajJcqcKrqGKXKKvu3", firstName: "Andrea", surname: "Nobili", completeName: "Andrea Nobili", birthDate: t, …}

These details are correctly rendered on the webpage:

https://i.sstatic.net/nwVeB.png

The potential issue lies in the fact that when the child component loads, the service method fetching data from Firestore may not have retrieved the data yet, causing it to attempt to render this line in the child component:

 <input id="disabled-input" type="text" pInputText [disabled]="disabled" [(ngModel)]="patientDetails.completeName" />

Since completeName is initially undefined, accessing this value triggers the error. Once the object is fully fetched from the database, the value becomes accessible.

To resolve this behavior and prevent these errors in the Chrome console, adjustments need to be made. Could you guide me on what changes should be implemented?

Answer №1

A timing issue is present here as the child will load before the asynchronous http request is finished.

To address this, there are several options available:

  1. Wrap the child code with *ngIf="patientDetails".
  2. Expand the usage of *ngIf in the parent component (where template display logic is handled).
  3. Set an initial valid value (such as an empty patient) to prevent errors.
  4. Utilize the safe navigation operator ? when accessing object properties in the child component like so: patientDetails?.completeName.

It is recommended to create an interface for your model and avoid using : any. This approach enables type checking and auto-completion features, especially with the right extensions in IDEs like Visual Studio Code.

Answer №2

My assumption aligns with yours:

I have a hunch that the issue arises from the child component being loaded before the service method retrieves data from Firestore. This results in an attempt to render a line into the child component prematurely:

The potential scenario is that the asynchronous task finishes after the instantiation of PatientDetailsInfoComponent. To address this, you can consider implementing a setter function for the @Input() decorator within your child component. By making this adjustment, the value of the patientDetails variable gets updated every lifecycle instead of just once:

private _patientDetails: any;
@Input() set patientDetails(value: any) {
  this._patientDetails = value;
}
get patientDetails(): any {
  return this._patientDetails;
}

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

Display Material UI icons as markers within Highcharts

Does anyone know how to use Material UI icons as markers in rendering? I have been searching for documentation but can't seem to find a clear explanation. Any guidance would be greatly appreciated! ...

Converting React useState to a JSON object data type

I imported a JSON data file using the following code: import data from "../data.json"; The contents of the file are as follows: [ { "name": "Emery", }, { "name": "Astrid", }, { " ...

Error encountered during Angular ng build -prod: The provided parameters do not align with any applicable call target signatures

Every time I try running ng build --target=production, an error pops up like this: ERROR in C:/Repo/NewCo/src/$$_gendir/app/fu/bar/fubar.component.ngfactory.ts (4011,35): Supplied parameters do not match any signature of call target. This error m ...

What could be causing my Angular 8 project to fail to start following the installation of Angular 10 CLI?

Previously, I was working on an Angular 8 project on my computer. However, I now need to install Angular 10 to run another project. To do so, I globally installed the new version with the following command: npm install -g @angular/cli After successfully ...

Adding a unique prefix for Angular2 routes in various environments

Imagine you are working on an Angular2 application in the development phase, and it is currently running smoothly on localhost:3000. All the routes are functioning properly. However, for deployment on myserver.com/myapp/, you need to add a prefix of myapp ...

Leverage Custom_Pipe within TS

I am currently working with a pipe that I have created. Here is the code: @Pipe({ name: 'searchNomES' }) export class SearchNomESPipe implements PipeTransform { transform(uos: IUo[], name?: string,): IUo[] { if (!uos) return []; if (!name) ret ...

Absent observable functions in the RxJS 5.0.0-beta.0 release

I am encountering an issue when trying to use RxJS with Angular 2. The methods recommended from the Typescript definition file are not present on my Observable object... https://i.stack.imgur.com/6qhS4.png https://i.stack.imgur.com/m7OBk.png After inves ...

The Angular service uses httpClient to fetch CSV data and then passes the data to the component in JSON format

I'm currently working on an Angular project where I am building a service to fetch CSV data from an API server and convert it to JSON before passing it to the component. Although the JSON data is successfully logged in the console by the service, the ...

Deprecated: Ngx Progress Bar will no longer be supported due to changes in

In the scenario below, I have noticed that BrowserXhr is outdated: { provide: BrowserXhr, useClass: NgProgressBrowserXhr } But when I refer to the documentation, it directs me to the main HttpClient page without showing an alternate provider example. Wha ...

Is searching for duplicate entries in an array using a specific key?

Below is an array structure: [ { "Date": "2020-07", "data": [ { "id": "35ebd073-600c-4be4-a750-41c4be5ed24a", "Date": "2020-07-03T00:00:00.000Z", ...

Exploring the functionalities of class methods within an Angular export function

Is it possible to utilize a method from an exported function defined in a class file? export function MSALInstanceFactory(): IPublicClientApplication { return new PublicClientApplication({ auth: AzureService.getConfiguration(), <-------- Com ...

Tips for concealing a dynamic table element in Angular 9

I have dynamically generated columns and rows in a table. Here is my HTML for the table component: <table id="tabella" class="table table-striped table-hover"> <thead class="thead-dark"> <tr> <th *ngFor="let header of _ob ...

Issue encountered with Angular 12 Material table: The data source provided does not match an array, Observable, or DataSource

When my REST API returns the following data: { "id": 1, "userId": 1, "date": "2020-03-02T00:00:02.000Z", "products": [ { "productId": 1, "quantity": 4 }, { "productId": 2, "quantity": 1 }, { "productId": 3, "quantity": 6 } ], "__v": 0 }, I attempt to imple ...

The router fails to navigate upon clicking after transitioning from beta to rc5 as a module

My routing configuration is as follows: import { Router, RouterModule } from '@angular/router'; import { HomeComponent } from './modules/home/home.component'; import { Step1Component } from './modules/step1/step1.component' ...

How can I switch the header bar button from "login" to "logout" in a React

I am currently working on a React and Next.js frontend template, specifically focusing on creating a dashboard with a header bar and login button. Once a user logs in, the login button should change to a logout button and the links in the header should als ...

Guide to importing a markdown document into Next.js

Trying to showcase pure markdown on my NextJS Typescript page has been a challenge. I attempted the following: import React, { useState, useEffect } from "react"; import markdown from "./assets/1.md"; const Post1 = () => { return ...

Switching to Angular's routing, prioritize removal of the previous component

I'm currently using [routerLink]="['my-route']" in my Angular project. However, I've encountered an issue with the routing system where it renders the new component first and then removes the old one from the DOM. This is caus ...

Troubleshooting: Angular 6 Renderer2 Issue with Generating Dynamic DOM Elements for SELECT-Option

Currently, I am attempting to dynamically create a select option using Renderer2. Unfortunately, I am facing difficulties in creating the <Select></Select> element, but I can confirm that the <options> are being successfully created. Due ...

`Navigating seamlessly across multiple Angular applications`

Contemplating the use of Angular in a corporate setting, I am thinking ahead to having multiple applications. Does Angular support the use of multiple applications and sharing components? For instance, can I share a Navigation component? Update (2/1/19): ...

The process of transitioning to a different view through a button press

How can I switch to another view by clicking a button in Aurelia? Here is my code: <button class="btn btn-success " click.delegate="save()">New item</button> Typescript configureRouter(config: RouterConfiguration, router: ...