Reputation: 89
I have an *ngFor directive that is functioning and displaying data in my browser as expected. Although my console in chrome is showing 'undefined' errors.
http.service.ts:
getExerciseProgress(exId: number): Observable<Exercise> {
return this.http.get<Exercise>(this.baseURL + 'exercises/GetProgressionByExerciseId/' + exId)
}
view-exercise.component.ts:
exercise: Exercise;
constructor(private http: HttpService) { }
ngOnInit() {
this.http.getExerciseProgress(7).subscribe(ex =>{
this.exercise = ex;
console.log(this.exercise);
});
}
The 7 passed as a paramater is for testing purposes and when I log the result, the object appears to be what I am looking for. An Exercise object with nested Progress array.
view-exercise.component.html:
<p *ngFor="let p of exercise.progress">{{ p.bpm }}</p>
The above line is the one throwing the following message in my Chrome console window. "ERROR TypeError: Cannot read property 'progress' of undefined". Despite this my browser is displaying the correct information.
Client side models (just in case its relevant):
export class Exercise {
id: number;
description: string;
progress: Progress[];
constructor(id: number, description: string, progress: Progress[]){
this.id = id;
this.description = description;
this.progress = progress;
}
export class Progress {
id: number;
dateAttempted: Date;
bpm: number;
constructor(id: number, dateAttempted: Date, bpm: number){
this.id = id;
this.dateAttempted = dateAttempted;
this.bpm = bpm;
}
}
Thanks in advance
Upvotes: 4
Views: 3107
Reputation: 12227
It's a race condition: when the template first loads, this.exercise
is still undefined
. Then, once the Observable resolves and a value is assigned, a change detection cycle is triggered, the *ngFor
is run and you see the values.
There are two typical patterns to fix this:
Use the elvis operator (probably best in your case because you only have that one accessor):
<p *ngFor="let p of exercise?.progress">{{ p.bpm }}</p>
?.
means "access the property to the right of this operator if the operand to the left is defined". Written this way, it's obvious that they can be chained: exercise?.progress?.timer?.started
Or use a guard on a container element, which better when you have many accessors and don't want to repeat ?.
everywhere:
<ng-container *ngIf="exercise">
<p *ngFor="let p of exercise.progress">{{ p.bpm }}</p>
<ng-container>
In the above example I used an <ng-container />
because it's not rendered into the DOM, but you can just as easily use that on a real element, like a <div />
. That's commonly used in the *ngIf="exercise; else no-data"
pattern, where #no-data
is another ng-template that replaces the div while you're loading your data.
FYI Side-note, because Angular uses polyfills, you can safely use template strings in your TypeScript. Meaning, you can write
this.baseURL + 'exercises/GetProgressionByExerciseId/' + exId
as
`${this.baseURL}/exercises/GetProgressionByExerciseId/${exId}`
which some people find easier to read.
Upvotes: 5
Reputation: 8478
Your DOM seems to be looking for the progress object inside exercise before the service call is finished and you have the data available.
You should loop over it when it's available , that means API call is finished and you have data:
<p *ngFor="let p of exercise?.progress">{{ p.bpm }}</p>
Or use *ngIf
to achieve it.
Upvotes: 0
Reputation: 1950
Can you try this :
<p *ngFor="let p of exercise?.progress">{{ p.bpm }}</p>
My guess is that angular try at first to display something even if your data is not init.
Upvotes: 0