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?