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

What is the best way to convert items from a foreach loop into a JSON string using the json_encode() function in PHP?

I want to populate a string with all the emails fetched from the database, in order to use JavaScript for checking if the email entered by a user in a form field is already registered. I'm attempting to utilize the json_encode() function. $connec ...

Testing Angular 2's IF condition with unit tests

I just started learning about unit testing and I am currently working on testing the if condition in the code snippet below: hide() { this.count --; if (this.count === 0) { this.loaderIs = false; } } My goal is to ...

Transforming a collection of Javascript objects into a pure Javascript array

After JSON.stringify-ing my JavaScript object, the output looks like this: [ { "item_id": null, "parent_id": "none", "depth": 0, "left": "1", "right": 4 }, { "item_id": "1", "parent_id": ...

Incorporating dynamic dependency injection in Angular 2 through the use of @Input()

Let's consider a scenario where an Angular 2 component-directive needs to dynamically determine the injected dependency it uses based on an @Input(). For example, I would like to be able to specify <trendy-directive use="'serviceA'"> ...

I'm currently working with ReactJS and attempting to retrieve JSON data from a REST API in JIRA, but I'm facing challenges in achieving this

I've been struggling for hours trying to understand why I am unable to access and transfer data in my array from the JSON data in JIRA using the REST API. Basically, I am attempting to retrieve the JSON data from the JIRA website via URL with Basic Au ...

Modify select options using JavaScript without losing the selected choice

I am attempting to update the options list of a select element using JavaScript and jQuery while retaining previously selected values. Below is the code I am using: var temp = $('#SelectName').chosen().val(); select = document.getEleme ...

Express encounters difficulty in processing Chunked Post Data

I am currently retrieving data from a Campbell Scientific data logger. This data is being posted to an application that is coded in Typescript using Express and BodyParser. The request successfully reaches the app (as I'm able to debug it), however, t ...

What is the best way to incorporate a 'category filter' in Angular2?

Unique Scenario In my Angular2 application, I have implemented code in a component's view parent.component.html that iterates through an array of items and generates a new component for each item: <div class="list-items"> <!-- The colored ...

Module `coc-tsserver` not found (error ts2307)

Working on a project using NeoVim with CoC for TypeScript development in a yarn-3 pnp-enabled environment. Suddenly, the editor stopped recognizing imports and started showing errors for non-existent modules (refer to the screenshot). I've already set ...

Issue Installing Npm Package (detected 23 security vulnerabilities)

My attempt to install the package resulted in an error message. How can I resolve this issue? ...

Obtaining Navigation Parameters within Custom React Navigation Title

In the React Navigation StackNavigator, I created a custom title that looks like this: const CustomStackNavigator = StackNavigator({ Home: { screen: HomeScreen } }, { navigationOptions: { headerTitle: <GradientHeader title={this.props.nav ...

What's causing the show/hide feature to malfunction within the for loop in Vue.js?

I've encountered an issue with my for loop where the show/hide functionality doesn't seem to work despite everything else functioning properly. I've been trying to troubleshoot this problem without success. <div id="app"> <ul> ...

How can I efficiently create an editForm in Angular?

Within my database, there are numerous users, each with their own collection of recipes. Each recipe contains various properties and a list of ingredients. Take a look at the screenshot below: Recipe with all properties When a user goes to edit a recipe ...

Struggling with the migration of routes from react-router v3 to v4

My route configuration using react-router v3 is as follows: <Route component={App}> <Route path="login" component={Login} /> <Route path="logout" component={Logout} /> <Route path="/" component={Admin}> <IndexRoute com ...

Tips on improving image loading speed in JavaScript code

I'm working on a simple weather map that changes the background image depending on the current weather status. However, I've noticed that there is a delay in changing the image when the weather status changes. I'm wondering if this delay is ...

Encountering an unusual issue: Unable to access undefined properties (specifically 'get')

I'm struggling to get the order history screen to display the order history of a specific user. Every time I navigate to the path, I encounter the error mentioned in the title. I double-checked the path for accuracy and made sure there are no spelling ...

Tips for expanding the functionality of the d3-selection module using TypeScript

I am currently working on a project that involves TypeScript and d3. I have already installed 'd3' and '@types/d3', and my use of d3 looks like this: import * as d3 from 'd3'; const x = d3.scaleLinear ... Everything was goin ...

Styling and theming in PrimeNG

Seeking advice on how to customize the appearance of the background for the primeNG panel component. I attempted to override the styling using the specific names in my scss file for the component, but it did not work. I also tried inline with <p-panel ...

Observing modifications in the database is possible after executing the setInterval function

I am using a JavaScript function that runs every 4 seconds with setInterval. Once this function is executed for the first time, it calls an ajax request and changes a column value in the database. I want to know how I can check if this value has been succe ...

When only showing the title to the client, it results in an undefined value

I have created a schema in mongoosejs that looks like this: var uploadSchema = mongoose.Schema({ title : String, happy : String, }); I am trying to show the data from my database on the client side (using ejs for templating) ...