当子组件尝试从父组件中检索到的对象呈现值时,为什么我会出现这种奇怪的行为?
Why am I obtaining this strange behavior when a child component try to render values from an object retrieved in the parent component?
我正在开发一个 Angular 应用程序,但在使用 @Input 装饰器将数据从父组件传递到子组件时遇到以下问题
我有一个名为 PatientDetailsComponent 的父组件。这是该组件的 TypeScript 代码:
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.loadPatientDetails();
this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
console.log("RETRIEVED PATIENT: ", patient);
this.patient = patient;
});
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
// Load the current patient details:
/*
async loadPatientDetails() {
this.patient$ = await this.patientService.getPatientByUID(this.patientUID)
console.log("patient$: ", this.patient$);
//this.loading = false;
}
*/
editPatientOptionOnChange(event, patientId) {
console.log("editPatientOptionOnChange START, event: ", event);
console.log("Patient ID: ", patientId);
this.selectedEditPatientId = patientId;
}
}
正如您在该父组件的 ngOnInit() 方法中看到的那样,我正在调用一个服务方法从 Firestore DB 检索患者信息,其中一个:
this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
console.log("RETRIEVED PATIENT: ", patient);
this.patient = patient;
});
此方法使用检索到的值设置 this.patient 字段。此检索到的对象必须传递给子组件。
这是我父组件的HTML代码:
<div class="container">
<p-selectButton [options]="editPatientOption"
[(ngModel)]="editPatientSelectedOption"
(onChange)="editPatientOptionOnChange($event, 5)"></p-selectButton>
<!--<div>{{(patient$ | async)?.completeName}}</div>-->
<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>
如您所见,根据 p-selectButton select 所做的选择,它将呈现为两个子组件之一.对于两者,我都将 patient 对象作为子组件的 @Input 传递。
例如,这是第一个子组件的 TypeScript 代码:
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 !!!");
}
}
为了检索父组件传递的对象,我有这一行:
@Input()
patientDetails: any
现在这个子组件的HTML代码包含:
<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>
如您所见,我正在声明一个 input 标签,它采用父组件传递给该子组件的模型对象的值。
这里我遇到了一个问题:在 Chrome 控制台中,我得到以下包含错误的输出:
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)
在这些错误之后,它打印 console.log() 的输出 p 定义到打印检索到的对象的子组件 ngOnInit() 方法中:
{id: "RmGajJcqcKrqGKXKKvu3", firstName: "Andrea", surname: "Nobili", completeName: "Andrea Nobili", birthDate: t, …}
并且此信息已正确呈现到我的页面中:
我怀疑问题是当加载子组件时,从 Firstore 检索数据的服务方法尚未检索数据,因此在开始时它尝试将此行呈现到子组件中:
<input id="disabled-input" type="text" pInputText [disabled]="disabled" [(ngModel)]="patientDetails.completeName" />
但 completeName 最初未定义。然后当从数据库中完全检索到对象时,我可以访问该值。
我的代码有什么问题?我错过了什么?我该如何解决这个错误行为,避免我的 Chrome 控制台出现所有这些错误?
我猜的和你一样:
I suspect that the problem is that when the child component is loaded the service method retrieving data from Firstore have not yet retrieved data so at the beginning it try to render this line into the child component:
可能发生的情况是 在 PatientDetailsInfoComponent
的实例化之后完成异步任务。您可以尝试为子组件中的 @Input()
装饰器使用 setter 函数。通过此更改,patientDetails
变量的值在每个生命周期都会更新,而不是仅更新一次:
private _patientDetails: any;
@Input() set patientDetails(value: any) {
this._patientDetails = value;
}
get patientDetails(): any {
return this._patientDetails;
}
是的,这是一个时间问题。 child 将在(异步)http 请求完成之前加载。
您有几种选择来处理此问题:
- 将 child 中的代码用
*ngIf="patientDetails"
括起来。
- 扩展 parent 中的
*ngIf
(您决定显示哪个模板)。
- 定义一个有效的初始值(空患者)。
- 访问 child 中的 object 属性时使用安全导航运算符
?
=> patientDetails?.completeName
.
无论如何,我建议为您的模型定义一个接口并避免 : any
。这将为您提供类型检查和自动完成功能(如果您有正确的扩展名,也在 html 中,假设 Visual Studio 代码)。
我正在开发一个 Angular 应用程序,但在使用 @Input 装饰器将数据从父组件传递到子组件时遇到以下问题
我有一个名为 PatientDetailsComponent 的父组件。这是该组件的 TypeScript 代码:
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.loadPatientDetails();
this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
console.log("RETRIEVED PATIENT: ", patient);
this.patient = patient;
});
});
}
ngOnDestroy() {
this.sub.unsubscribe();
}
// Load the current patient details:
/*
async loadPatientDetails() {
this.patient$ = await this.patientService.getPatientByUID(this.patientUID)
console.log("patient$: ", this.patient$);
//this.loading = false;
}
*/
editPatientOptionOnChange(event, patientId) {
console.log("editPatientOptionOnChange START, event: ", event);
console.log("Patient ID: ", patientId);
this.selectedEditPatientId = patientId;
}
}
正如您在该父组件的 ngOnInit() 方法中看到的那样,我正在调用一个服务方法从 Firestore DB 检索患者信息,其中一个:
this.patientService.getPatientByUID(this.patientUID).subscribe(patient => {
console.log("RETRIEVED PATIENT: ", patient);
this.patient = patient;
});
此方法使用检索到的值设置 this.patient 字段。此检索到的对象必须传递给子组件。
这是我父组件的HTML代码:
<div class="container">
<p-selectButton [options]="editPatientOption"
[(ngModel)]="editPatientSelectedOption"
(onChange)="editPatientOptionOnChange($event, 5)"></p-selectButton>
<!--<div>{{(patient$ | async)?.completeName}}</div>-->
<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>
如您所见,根据 p-selectButton select 所做的选择,它将呈现为两个子组件之一.对于两者,我都将 patient 对象作为子组件的 @Input 传递。
例如,这是第一个子组件的 TypeScript 代码:
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 !!!");
}
}
为了检索父组件传递的对象,我有这一行:
@Input()
patientDetails: any
现在这个子组件的HTML代码包含:
<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>
如您所见,我正在声明一个 input 标签,它采用父组件传递给该子组件的模型对象的值。
这里我遇到了一个问题:在 Chrome 控制台中,我得到以下包含错误的输出:
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)
在这些错误之后,它打印 console.log() 的输出 p 定义到打印检索到的对象的子组件 ngOnInit() 方法中:
{id: "RmGajJcqcKrqGKXKKvu3", firstName: "Andrea", surname: "Nobili", completeName: "Andrea Nobili", birthDate: t, …}
并且此信息已正确呈现到我的页面中:
我怀疑问题是当加载子组件时,从 Firstore 检索数据的服务方法尚未检索数据,因此在开始时它尝试将此行呈现到子组件中:
<input id="disabled-input" type="text" pInputText [disabled]="disabled" [(ngModel)]="patientDetails.completeName" />
但 completeName 最初未定义。然后当从数据库中完全检索到对象时,我可以访问该值。
我的代码有什么问题?我错过了什么?我该如何解决这个错误行为,避免我的 Chrome 控制台出现所有这些错误?
我猜的和你一样:
I suspect that the problem is that when the child component is loaded the service method retrieving data from Firstore have not yet retrieved data so at the beginning it try to render this line into the child component:
可能发生的情况是 在 PatientDetailsInfoComponent
的实例化之后完成异步任务。您可以尝试为子组件中的 @Input()
装饰器使用 setter 函数。通过此更改,patientDetails
变量的值在每个生命周期都会更新,而不是仅更新一次:
private _patientDetails: any;
@Input() set patientDetails(value: any) {
this._patientDetails = value;
}
get patientDetails(): any {
return this._patientDetails;
}
是的,这是一个时间问题。 child 将在(异步)http 请求完成之前加载。
您有几种选择来处理此问题:
- 将 child 中的代码用
*ngIf="patientDetails"
括起来。 - 扩展 parent 中的
*ngIf
(您决定显示哪个模板)。 - 定义一个有效的初始值(空患者)。
- 访问 child 中的 object 属性时使用安全导航运算符
?
=>patientDetails?.completeName
.
无论如何,我建议为您的模型定义一个接口并避免 : any
。这将为您提供类型检查和自动完成功能(如果您有正确的扩展名,也在 html 中,假设 Visual Studio 代码)。