1

So I'm new to angular... This code here:

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

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

So first time this thing opens I get "TypeError: Cannot read property 'src' of undefined" And that console.log shows "undefined", but when I click home in the app and then click the same thing it works properly... Logs the right 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 has a list of products that was previusly loadedd from a .json file

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

That module is used to make a grid of products so to avoid loading the .json twice I just imported it and reused it.

So if anyone can explain why it doesn't work the first time after loading the page (using ng serve of course) and why it does work every time after that, I'd really love you.

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

<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);
  }
}

  • That error is in your template .html, I think: "TypeError: Cannot read property 'src' of undefined". Show us all the component code and the template. There isn't a "src" property in your code. – Sandman May 07 '20 at 15:07
  • Inside the json there is a src here is an example of one of the items inside the .json: { "id": 1, "src": "assets/Images/Angel.png", "alt": "Angel", "name": "Angel", "price": 50.99, "description": ""}, – ihavenowingss May 07 '20 at 15:16

4 Answers4

3

This is what Resolvers are used for. It allows you to pre-load data before the component is initialized.

Here is an example 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'));
  }
}

Then, you apply it to your routes:

const routes = [
    {
        path: 'products/:id'
        component: ProductComponent,
        resolve: {
            product: ProductsResolver
            //you can add more resolvers here too!
        }
    }
]

Last, you can access the data in your component:

constructor(private route: ActivatedRoute) {}

ngOnInit() {
  this.product = this.route.snapshot.data.product;
}
spots
  • 2,483
  • 5
  • 23
  • 38
0

Use the optional chaining operator (the "question mark") in the template:

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

Frequently referred to as "elvis operator", it will avoid errors when product is null or undefined.

Of course, I'm supposing that, eventually, product will have some value. This is useful when, for example, you'll go to the database to get your product and, while this is happening, product is undefined.

[UPDATE]: Considering the last updates made to the question:

ngOnInit() {
  this.getJSON().pipe(map(data => data.products)).subscribe(products => {
    this.products = products;
    this.product = this.products.find(product => product.id === id);
  });
}
julianobrasil
  • 8,954
  • 2
  • 33
  • 55
  • This does avoid an error but the image still won't display on the first try. – ihavenowingss May 07 '20 at 15:26
  • Yeah, the next question I was gonna make was if `this.products.getProduct(id)` wasn't an `Observable`. But it was already answered. I'm gonna consider that in my answer just for completeness, as I think `Elvis operator` is a great thing concerning failure proof code. – julianobrasil May 07 '20 at 16:22
0

Whenever there is a subscription involved, it should be noted that asynchronous data are involved. You could read more about them here.

Essentially the data are not returned immediately like a normal function. You are subscribing to get the data. But ultimately the observable determines when you receive it.

Here the getProduct() functions assumes the this.products variable is defined already, but that might not be the case.

I assume you are subscribing to the observable in the service. I'd advice you to subscribe in the component.

So in this case you could move everything including the subscription to the OnInit hook and remove the subscription from the service. Try the following

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);
}
ruth
  • 29,535
  • 4
  • 30
  • 57
0

You can use resolvers to solve this problem. This is how you do it - https://codeburst.io/understanding-resolvers-in-angular-736e9db71267

Timothy
  • 3,213
  • 2
  • 21
  • 34