A tutorial on ensuring Angular loads data prior to attempting to load a module

Just starting my Angular journey... Here's some code snippet:

  ngOnInit(): void {
    this.getProduct();
  }

  getProduct(): void {
    const id = +this.route.snapshot.paramMap.get('id');
    this.product = this.products.getProduct(id);
    console.log(this.product);
  }

When I first open this, I get "TypeError: Cannot read property 'src' of undefined" The console.log shows "undefined", but when I navigate home and then come back, it works perfectly... Logs the correct item and displays the right image

public getProduct(id: number): Product {
   // console.log('returning product');
   return (this.products.find(product => product.id === id));
 }

The module that returns the list of products was previously loaded from a .json file

  constructor( private http: HttpClient ) {
    this.getJSON().subscribe(data => {
      this.products = data.products;
     });
  }

This module is used to create a grid of products, so to avoid loading the .json file twice, I imported it and reused it.

If anyone can explain why it doesn't work the first time after loading the page (using ng serve) and why it works fine every subsequent time, I would greatly appreciate it.

Edit, here is the template and the entire component.ts as requested:

<div>
<img src="{{product.src}}" alt="">
</div>
import { Component, OnInit } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Location } from '@angular/common';

import { Product } from '../classes/product.interface';
import { ShoppingGridComponent } from '../shopping-grid/shopping-grid.component';

@Component({
  selector: 'app-product',
  templateUrl: './product.component.html',
  styleUrls: ['./product.component.css']
})
export class ProductComponent implements OnInit {
  product: Product;
  constructor(
    private route: ActivatedRoute,
    private location: Location,
    private products: ShoppingGridComponent
  ) {}
  ngOnInit(): void {
    this.getProduct();
  }

  getProduct(): void {
    const id = +this.route.snapshot.paramMap.get('id');
    console.log(id);
    this.product = this.products.getProduct(id);
    console.log(this.product);
  }
}

Answer №1

The purpose of utilizing Resolvers is to preload data prior to component initialization.

Below is an illustration of a resolver:

@Injectable({ providedIn: 'root' })
export class ProductsResolver implements Resolve<Product> {
  constructor(private http: HttpService) {}

  resolve(
    route: ActivatedRouteSnapshot,
    state: RouterStateSnapshot
  ): Observable<any>|Promise<any>|any {
    return this.http.get('api/v1/products/' + route.paramMap.get('id'));
  }
}

You can then assign it to your routes:

const routes = [
    {
        path: 'products/:id'
        component: ProductComponent,
        resolve: {
            product: ProductsResolver
            //additional resolvers can also be included here!
        }
    }
]

Finally, you can retrieve the data in your component:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.product = this.route.snapshot.data.product;
}

Answer №2

When dealing with subscriptions, it's important to understand that asynchronous data is at play. If you want to learn more about how they work, check out this resource.

With asynchronous data, the information isn't immediately returned like a normal function call. Instead, you subscribe to receive the data and it's up to the observable to decide when you get it.

In the code snippet provided, the getProduct() function assumes that the this.products variable is already defined, but that may not always be the case.

If you're subscribing to the observable in the service, I recommend moving the subscription logic to the component instead.

You can achieve this by consolidating everything, including the subscription, within the OnInit hook in the following manner:

Component


products: Product[] = [];

ngOnInit(): void {
  this.getJSON().subscribe(data => {
    this.products = data.products;
    
    this.getProduct();
  });
}

getProduct(): void {
  const id = +this.route.snapshot.paramMap.get('id');
  this.product = this.products.getProduct(id);
  console.log(this.product);
}

Answer №4

To prevent errors, incorporate the optional chaining operator (also known as the "question mark") in your template code:

<div>
  <img src="{{product?.src}}" alt="">
</div>

Commonly referred to as the "elvis operator," this technique ensures that no errors will occur if the product variable is null or undefined.

This method assumes that eventually, a value will be assigned to product. It is especially useful when you are retrieving product information from a database, and during the retrieval process, product remains undefined.

[UPDATE]: With the latest modifications to the original question, consider the following updated code snippet:

ngOnInit() {
  this.getJSON().pipe(map(data => data.products)).subscribe(products => {
    this.products = products;
    this.product = this.products.find(product => product.id === id);
  });
}

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

receiving null instead of an identifier

Attempting to perform an update operation in Angular. Upon submission after updating, a random number is displayed at the end of the API rather than the specific id number. The request URL appears as follows Request URL: http://localhost:4200/api/auth/rol ...

Facing challenges with parsing JSON files in an Angular 2+ application

Utilizing the Angular CLI, I have configured this project with the standard folder layout. My goal is to practice reading a JSON file from src/app/assets/items.json and then using it to display these items in the HTML. items.json: { "results": [ ...

Getting started with testing in Angular 2

When I first started coding my app, unit tests were not on my mind. Now, I realize the importance and need to write them. However, while attempting to write tests using Jasmine only, I encountered an error when trying to import components - "system is no ...

After the introduction of ReactiveFormsModule, the functionality of the Angular router has ceased

I am working on setting up a reactive form in Angular for a login page. Here is my login form: <form [formGroup]="loginForm" (ngSubmit)="login(loginForm.value)"> <div class="form-group"> <label for="username">Username</label> ...

Encounter the error message "Unable to resolve all parameters" after including the Interceptor

Currently, I am in the process of implementing HttpInterceptor functionality. However, upon adding it to @NgModule, I encountered the following error message in Chrome: Uncaught Error: Can't resolve all parameters for JwtInterceptor: (?, ?). at s ...

Error message displaying 'class-transformer returning undefined'

I'm new to working with the class-transformer library. I have a simple Product class and JSON string set up to load a Product object. However, I'm encountering an issue where even though I can see the output indicating that the transformation was ...

Converting jQuery drag and drop functionality to Angular: A step-by-step guide

I have implemented drag and drop functionality using jquery and jquery-ui within an angular project. Below is the code structure: Index.html, <!doctype html> <html lang="en"> <head> <link href="//code.jquery.com/ui/1.10.3/themes/ ...

Recently updated from Angular 9 to 14 and noticed a peculiar issue in the deployed app - all API calls seem to only reference the root or hosted URL in the request URL

After upgrading the application from angular 9 to angular 14, I encountered a network call issue. The application was successfully deployed via azure devops, but all network calls were directed to the host URL instead of the expected API endpoints. For exa ...

The TypeScript 'object' type

My query regarding the definition of TypeScript's {} type has brought about some confusion. Initially, I believed it represented an "empty object with no properties," but I recently encountered an ESLint rule that prohibits the use of {} type because ...

slider malfunctioning when dir="rtl" is used

Looking to incorporate Arabic language support into an Angular Material site, but encountering issues with the mat slider when applying dir="rtl". The problem arises when dragging the thumb in a reverse direction. I attempted a solution that resulted in a ...

Blending Angular5 and AngularJS in Polymer

We are considering launching two new projects - one using Angular 5 and the other utilizing Polymer. The second project is intended to serve as a component library for reuse in not only the Angular 5 project but also in other AngularJS projects. After res ...

There seems to be an issue with the React Native FlatList: It appears that there is no overload matching this call and some

I am currently learning React Native and attempting to create a basic chat room application. I am facing an issue with the FlatList component that I can't seem to resolve. Even though I have provided both the data prop and renderItem prop to the FlatL ...

There is an issue with the Svelte TypeScript error related to the $: syntax, specifically stating that a declaration cannot be used in a

I encountered an issue where I am receiving the error message "Cannot use a declaration in a single-statement context" while using $: syntax in a script with lang="ts" within my +page.svelte file. Additionally, when I check the version control system (VCS) ...

What steps can I take to eliminate the overload error that occurs when I extend the Request object in Express?

I'm having trouble extending Express' Request object to access req.user, and I'm encountering an overload error. I've attempted a couple of solutions, but none of them seem to work for me. EDIT: I am currently using Passport.js and JWT ...

"Navigate to another screen with the FlatList component upon press, displaying specific

I've created a flatlist of countries with a search filter using an API. I need help implementing a feature where clicking on a country's name in the list redirects to a screen that displays the country's name and number of cases. The screen ...

The concept of 'this' remains undefined when using a TypeScript Property Decorator

I've been delving into TypeScript decorators focusing on properties, and I crafted the following snippet inspired by various examples: decorator.ts export function logProperty(target: any, key: string) { let val = this[key]; const getter = () ...

What is the process for configuring a TimePicker object in antd to send a Date with UTC+3 applied?

I have Form.Item and a TimePicker defined inside it. I am using this form to send a POST request, but when I try to post 16:35, it gets sent as 13:35. The UTC offset is not being considered. However, the CreationTime works fine because it utilizes the Da ...

Upgrading Angular from version 5 to 6 resulted in the Angular.json file not being generated

As I follow the official guide to upgrade my Angular app to version 10, I am currently facing an issue while trying to upgrade to CLI version 6 following the instructions on update.angular.io. It is important to ensure that you are using Node 8 or later. ...

Navigating to a specific div within a container with vertical overflow in an Angular application

I am working on an angular application where I have a left column with a scrollable div that has overflow-y: auto;, and a right column with bookmark links that jump to sections within the scrollable container. I am currently facing two challenges: 1 - Co ...

Is it possible to draw parallels between Java Callable and Angular Observable (RxJS) in terms of client implementation?

Before anyone considers downvoting or closing my question, I want to clarify that I am not asking which option is better (as this would be an irrelevant question considering one focuses on the server and the other on the browser). In this straightforward ...