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

Getting the route parameter in Angular 2 is easy with the stable router

Currently working with the latest stable Angular 2 RC version. Unfortunately, the documentation for the new router component has yet to be completed. Struggling to retrieve a parameter from a navigated page. Defined routes: @Routes([ {path: '/resu ...

Ionic 4 and RxJS 6: Issue with Ionic-native HTTP get method not fetching data as expected

Currently, I am in the process of transitioning from using the angular HttpClient Module to the ionic-native HTTP Module. This switch is necessary because I have encountered difficulties accessing a third-party API using the standard HttpClient Module. To ...

Steps for launching Angular 5 application using Node.js server

I have developed an Angular 5 application that retrieves data from a node.js server. I successfully deployed the application to my web server hosted by FastComet, which supports node.js, but unfortunately, the server does not seem to be functioning properl ...

Enhance Material UI with custom properties

Is it possible to add custom props to a Material UI component? I am looking to include additional props beyond what is provided by the API for a specific component. For example, when using Link: https://material-ui.com/api/link/ According to the document ...

Simplifying parameter types for error handling in app.use callback with Express.js and TypeScript

With some familiarity with TypeScript but a newcomer to Express.js, I aim to develop a generic error handler for my Express.js app built in TypeScript. The code snippet below is functional in JavaScript: // catch 404 and forward to error handler app.use((r ...

I'm encountering difficulties utilizing ternary operators in TypeScript

I am struggling with ternary operators in TypeScript and need help understanding the issue. Please review the code below: const QuizQuestionContainer = ({ qa }: QuizQuestionContainerPropsType) => { const { question, option1, option2, option ...

What is the best way to emphasize when the path matches exactly with '/'?

Is there a way to highlight the path only when it exactly matches '/'? Currently, even on 'Page 2', the 'Home' link is still highlighted. Check out the plunker here .active { color: red; } <a routerLinkActive="active" r ...

Angular Reactive Forms can efficiently manage checkboxes that are organized in two levels or dimensions

As I work with Angular 6/7 and utilize reactive forms, I am faced with a form containing a list of permissions represented by two checkboxes in each row. Initially, the second checkbox within the row should be disabled upon first loading, only becoming ena ...

Angular service is able to return an Observable after using the .then method

I am currently facing an issue with retrieving the authentication status in a service method. Everything seems to be working fine except for the return statement. I am struggling with the usage of .then inside .map and I am unable to figure out how to retu ...

How can one effectively monitor the progress of multiple ajax requests in Angular8 for optimal professionalism?

There is a single completed variable made up of 6 parts, outlined below: selectedCandidateData: Candidate = { candidateBasic: null, experienceList: null, educationList: null, certificationList: null, candidateAbout: null, }; Each part ...

Interacting between two Angular 4 applications

I came across solutions here and here related to this issue, but they seem to be for an older beta version of Angular (I believe now we should bootstrap a module, not a component; also, I couldn't find the bootstrap function in the documentation for v ...

Typescript offers a feature where we can return the proper type from a generic function that is constrained by a lookup type,

Imagine we have the following function implementation: type Action = 'GREET' |'ASK' function getUnion<T extends Action>(action: T) { switch (action) { case 'GREET': return {hello: &a ...

What could be the reason for TypeScript inferring the generic type as an empty string?

I have developed a React component known as StateWithValidation. import { useStateWithValidation } from "./useStateWithValidation"; export const StateWithValidation = () => { const [username, setUserName, isValid] = useStateWithValidation( ( ...

Specialized typescript function that is compatible with extended interfaces

Is there a way to create a versatile function that can be applied to any interface derived from a top-level interface? This function should take an unpersisted interface (without an id property) and return a persisted one (with an id property). The two ma ...

The result of comparing with `instanceof` in TypeScript

class Department { name: string; constructor(n: string) { this.name = n; } describe(this: Department){ console.log('department: ' +this.name); } } const frontend = new Department('frontend'); frontend.describe(); con ...

Use PipeTransform to apply multiple filters simultaneously

Is it possible to apply multiple filters with PipeTransform? I attempted the following: posts; postss; transform(items: any[]): any[] { if (items && items.length) this.posts = items.filter(it => it.library = it.library ...

Utilizing a function within the catchError method

A function has been defined to handle errors by opening a MatSnackBar to display the error message whenever a get request encounters an error. handleError(err: HttpErrorResponse) { this.snackBar.open(err.message) return throwError(err) } When subs ...

Exploring Angular 2's ngFor Directive with JSON Data

Recently diving into Angular2, I've been trying to extract data from a JSON file. While I have successfully retrieved the file using a REST client, stored it in a local variable within a component, and accessed certain properties of that variable, I&a ...

Loading SVG templates dynamically in Angular can be done by utilizing a string

Is it possible to convert SVG content loaded from a server into a component template in Angular, with the ability to bind properties to it? For example, <tspan [attr.color]="textColor" ...>{{myText}}</tspan>. I have tried loading and ...

Getting around relative paths in an Angular application hosted by NGINX

I am using NGINX to host my Angular application. The frontend is accessible at http://localhost/, and the backend can be accessed at http://localhost/api/. While most of my configuration works correctly, I am encountering an issue with a relative path in a ...