Exploring Angular 8 HTTP Observables within the ngOnInit Lifecycle Hook

Currently, I am still a beginner in Angular and learning Angular 8.

I am in the process of creating a simple API communication service to retrieve the necessary data for display. Within my main component, there is a sub-component that also needs to fetch data for loading.

Though I have followed various tutorials, I keep encountering a recurring issue where the component loads before the API HTTP request completes, resulting in undefined data.

My current API service utilizes HttpClient for communication with the API:

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';
import { Observable, throwError } from 'rxjs';
import { retry, catchError } from 'rxjs/operators;

@Injectable({
    providedIn: 'root'
})
export class ApiService {
    constructor(private http: HttpClient) {}

    getUserFeed(id: number): Observable<Post[]> {
        return this.http
        .get<Post[]>(`${API_URL}/feed`)
        .pipe(
            retry(3),
            catchError(this.handleError)
        );
    }

    getProfile(id: number): Observable<Profile> {
        return this.http
        .get<Profile>(`${API_URL}/profile/${id}`)
        .pipe(
            retry(3),
            catchError(this.handleError)
        );
    }

    handleError(error: any) {
        let errorMessage: string;
        // Set error message
        (error.error instanceof ErrorEvent) ?
            errorMessage = error.error.message :
            errorMessage = `Error Code: ${error.code}\nMessage: ${error.message}`;
        console.log(errorMessage);
        return throwError(errorMessage);
    }
}

The expected response from the API should be an array of Posts.

In my component, I make use of this service as follows:

import { Component, OnInit } from '@angular/core';
import { UserService } from '../user/user.service';
import { ApiService } from '../api/api.service';
import { User } from '../user';
import { Post } from '../Post';

@Component({
    selector: 'app-feed',
    templateUrl: './feed.component.html',
    styleUrls: ['./feed.component.css'],
})
export class FeedComponent implements OnInit {
    posts: Post[] = [];
    user: User;
    post: Post;

    constructor(private userService: UserService) {
        this.user = this.userService.user;
    }

    public ngOnInit() {
        this.userService.getUserFeed(this.user.id).subscribe((feed) => {
            this.posts = feed;
            console.log(this.posts);
        });
    }
}

The HTML template of my component loops through these posts and passes them to the sub-components:

<div *ngIf="posts.length">
    <mat-list *ngFor="let post of posts">
        <!-- Post Display -->
        <app-post-display [post]=post></app-post-display>

        <!-- Post Interaction Row -->
        <app-post-interaction-bar [post]=post></app-post-interaction-bar>

        <!-- Comment Preview -->
        <app-comment-preview [post]=post></app-comment-preview>
        <mat-divider></mat-divider>
    </mat-list>
</div>

While everything seems fine with displaying posts in the main component, I encounter an issue within the sub-component app-post-display, which retrieves the author information from the post.authorId property.

I have initialized the author variable and placed the logic to fetch author data in the ngOnInit function, but unfortunately, I consistently receive

ERROR TypeError: Cannot read property 'id' of undefined
in the console. It appears that the component attempts to display before fetching the author data.

What adjustments do I need to make to ensure the author data is fetched prior to loading the component display?

import { Component, Input, OnInit } from '@angular/core';
import { UserService } from '../user/user.service';
import { User } from '../user';
import { Post } from '../post';
import { Profile } from '../profile';
import { ApiService } from '../api/api.service';


@Component({
    selector: 'app-post-display',
    templateUrl: './post-display.component.html',
    styleUrls: ['./post-display.component.css'],
})
export class PostDisplayComponent implements OnInit {
    @Input() post: Post;
    user: User;
    author: Profile;

    constructor(private userService: UserService, private backend: BackendService) {
        this.user = this.userService.user;
    }

    ngOnInit() {
        this.backend.getProfile(this.post.authorId).subscribe((profile) => {
            this.author = profile;
            console.log(this.author);
        });
    }
}

Answer №1

ngOnInit within the Child Component will only execute once. Keep in mind that you cannot assume that the post is defined right from the start.

To resolve this issue, consider moving your invocation to ngOnChanges and validate if post has a value before processing. Try implementing the following code snippet:

import { Component, Input, OnChanges } from '@angular/core';
import { UserService } from '../user/user.service';
import { User } from '../user';
import { Post } from '../post';
import { Profile } from '../profile';
import { ApiService } from '../api/api.service';

@Component({
  selector: 'app-post-display',
  templateUrl: './post-display.component.html',
  styleUrls: ['./post-display.component.css'],
})
export class PostDisplayComponent implements OnChanges {
  @Input() post: Post;
  user: User;
  author: Profile;

  constructor(
    private userService: UserService, 
    private backend: BackendService
  ) {
    this.user = this.userService.user;
  }

  ngOnChanges() {
    if (this.post) {
      this.backend.getProfile(this.post.authorId).subscribe((profile) => {
        this.author = profile;
        console.log(this.author);
      });
    }
  }
}

Alternatively, you can implement this logic in your Parent Component:

<div *ngIf="posts">
    <mat-list *ngFor="let post of posts">
        <!-- Display Post Information -->
        <app-post-display [post]=post></app-post-display>

        <!-- Interact with Post -->
        <app-post-interaction-bar [post]=post></app-post-interaction-bar>

        <!-- Preview Comments -->
        <app-comment-preview [post]=post></app-comment-preview>
        <mat-divider></mat-divider>
    </mat-list>
</div>

Ensure that you do not initialize posts with an empty array at the beginning.

Answer №2

After much research, I discovered that the most effective solution to my issue involved implementing a resolver prior to redirecting to the page (https://angular.io/api/router/Resolve).

By utilizing this method, the data was able to load before the page finished rendering, preventing any potential errors.

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

Adding a custom class to an ng-bootstrap tooltip can be accomplished by utilizing Angular's

Having trouble customizing an ng-bootstrap tooltip with a custom class. Markup: <i class="fa fa-info-circle" aria-hidden="true" [ngbTooltip]="infoTooltipTemplate" [tooltipClass]="info-tooltip" placement="top"></i> Stylesheet: .info-tooltip ...

Tips on preventing duplication of APIs when retrieving data using nextjs

In my code, I have a function that can be called either from server-side rendering or client side: export const getData = async (): Promise<any> => { const response = await fetch(`/data`, { method: 'GET', headers: CONTENT_TYPE_ ...

Transfer responsibilities of events to the canvas and then fetch the Element in the handler

Currently, I am utilizing the Raphaël library to create a network graph, where nodes are depicted as circles. Users have the ability to dynamically add nodes by clicking on the canvas. When a new node is added, an Element object is pushed into both a Set ...

Transforming PHP shortcode into JQuery functionality

My website is built on Wordpress, and I use javascript to load some of the content. Here's an example: jQuery(".portfolio-fs-slides").css({"display":"none"}).prepend('<div class="portfolio-fs-slide current-slide portfolio-ppreview"><d ...

Dynamic way to fetch computed properties in VueJS

I am trying to find a way to calculate the sum of computed properties that begin with the string calculateSum. The challenge is that I cannot access their names using this.computed. Here is my approach: getSubTotal(){ var computed_names = []; var ...

How to display document files (.doc or .docx) using a byte array in Angular 4

I am facing a challenge in viewing all the attachments submitted by users. While I can easily view PDF and image files, I seem to have trouble with files having .doc or .docx extensions. Here is my current approach: let file = null; if (extension === &a ...

Comparing Two Arrays in AngularJS and Disabling on Identical Cases

I currently have two tables: "Available subject" and "Added subject" which are populated by ng-repeat with two separate lists of objects. My objective is to accomplish three things: 1 - When the user clicks on the plus sign, a row with identical content s ...

Can you extract information from the XML file and modify the anchor tags?

<description> <div class="field field-name-field-image field-type-image field-label- hidden"><div class="field- items"><div class="field-item even"><a href="/news/news/vg"> ...

Using an array as a reference can lead to failure when dealing with asynchronous calls

As I delve into referencing a document in MongoDB, my process involves first creating the document to insert before interfacing with the database itself. Upon initial setup: const venues = [ new Venue({ name: 'A' }), ]; const events = [ new ...

Trouble deploying Firebase Cloud Functions

I am attempting to implement the following two functions: exports.increaseWaitinglistCounters = functions.database .ref('waitinglists/$iid/$uid') .onCreate(async () => { await admin .database() .ref(`waitinglistCounters/$ii ...

Looking for help in resolving console error displaying 'Uncaught (in promise)' notification

I've encountered an issue while trying to troubleshoot a problem that involves using the find() method, which is causing an error. import { CART_ADD_ITEM, CART_REMOVE_ITEM } from '../constants/cartConstant'; const cartReducers = (state = { ...

How to Refresh EJS Template in an Express Node.js Application

Currently, I am integrating discord.js with express and facing a challenge. I want my webpage to automatically update whenever the client.on("message") event is triggered. According to my knowledge and research, it seems that rendering an ejs template is o ...

What is the best way to incorporate jQuery's default handsontable into an AngularJS directive?

I currently have a handsontable functioning in a non-AngularJS application, and I am in the process of developing a new version of the software that heavily utilizes AngularJS (SPA). My question is: is it possible to encapsulate the existing handsontable ...

One helpful tip for adjusting the size of a UI chip on the fly

I am attempting to adjust the size of a UI chip dynamically based on the font size of its parent elements using the em unit in CSS. My objective is to achieve something like this: style={{size:'1em'}} The issue I'm encountering: The chip e ...

Creating a circular shape around a specific location on a map is a common task in Openlayers. Here's a

I have been attempting to create a circle based on the point/coordinate where a user clicks. While I know how to generate a point and found a function that can create a circle based on said point (similar to a buffer or range ring), it appears to only func ...

Loop Swig with Node.js and Express!

I'm attempting to create a loop in order to access array objects using swig. The goal is to make a loop that checks the object's length. I am able to access the objects by {{styles[0].style}}, where [] represents an array. So, essentially what I ...

Utilizing an Application Programming Interface in React Native

Recently diving into React Native, I embarked on creating a basic app leveraging the Marvel API along with an API wrapper. My aim is to implement an infinite scroll view using VirtualizedList. Here's where I could use some guidance: What should be pas ...

Remove observableB if it is triggered 1 second after observableA

Imagine having two different observables, known as observableA and observableB. There are 3 possible scenarios for how these observables can be activated: only observableA is activated. only observableB is activated. observableA is activated first, follo ...

Tips on working with an array received from a PHP script through AJAX

I've been stuck with this issue for the past few hours and I'm hoping to find a solution here. What I'm attempting to do is something like the following: PHP: $errorIds = array(); if(error happens){ array_push($errorIds, $user['user ...

Interactive Show Map with Autocompletion Feature

I attempted to implement autocompletion for my application and integrate it with Google Maps, but I'm encountering issues. The code for autocompletion is located in a separate JavaScript file called autocomplete.js. Since I already have a Google Map ...