0

I'm following Mosh Hamedani tutorial "Angular 4: Beginner to Pro".

I'm trying to show the title of a product in a form when trying to edit it. The product is stored in a firebase database. I'm new to Angular.

However, I'm getting this error in the console when I go to the edit form

ERROR TypeError: Cannot read property 'title' of undefined
    at Object.eval [as updateDirectives] (ProductFormComponent.html:7)
    at Object.debugUpdateDirectives [as updateDirectives] (core.js:23911)
    at checkAndUpdateView (core.js:23307)
    at callViewAction (core.js:23548)
    at execComponentViewsAction (core.js:23490)
    at checkAndUpdateView (core.js:23313)
    at callViewAction (core.js:23548)
    at execEmbeddedViewsAction (core.js:23511)
    at checkAndUpdateView (core.js:23308)
    at callViewAction (core.js:23548)

Here is part of my form :

<div class="form-group">
            <label for="title">Title</label>
            <input #title="ngModel" [(ngModel)]="product.title" <== error here
name="title" id="title" type="text" class="form-control" required>
            <div class="alert alert-danger" *ngIf="title.touched && title.invalid">
              Title is required
            </div>
          </div>

And here is the product-form.component.ts

import { Component, OnInit, OnDestroy } from '@angular/core';
import { CategoryService } from 'src/app/category.service';
import { ProductService } from 'src/app/product.service';
import { Router, ActivatedRoute } from '@angular/router';
import { take } from 'rxjs/operators';

@Component({
  selector: 'app-product-form',
  templateUrl: './product-form.component.html',
  styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent implements OnInit {

  categories$;
  product;

  constructor(
    categoryService: CategoryService,
    private route: ActivatedRoute,
    private productService: ProductService,
    private router: Router) {
    this.categories$ = categoryService.getCategories();

    let id = this.route.snapshot.paramMap.get('id');
     if (id) this.productService.get(id).snapshotChanges().pipe(take(1))
.subscribe(p => this.product = p); <== this does not seem to be working
  }

  save(product) {
    this.productService.create(product);
    this.router.navigate(["/admin/products"]);
  }

  ngOnInit() {
  }

}

What can I do to show the product's title?

Nicolas
  • 103
  • 3
  • 12

5 Answers5

0

You can also use the

product:any = {};
0

Changing

product;

to

product:any = {};

and (valueChanges() instead of snapshotChnages())

if (id) this.productService.get(id).snapshotChanges().pipe(take(1))
.subscribe(p => this.product = p); 
  }

to

if (id) this.productService.get(id).valueChanges().pipe(take(1))
.subscribe(p => this.product = p); <== this does not seem to be working
  }

have fixed the issue

Nicolas
  • 103
  • 3
  • 12
0

The error is because the value is not initialized on startup. This happens when the variable gets its first value from a subscription, which takes sometime to get a value, so the view renders and tries to get the value before it exists. I would recommend first creating a model for your data, then initializing your model Take a look at Creating model classes in typescript on how to create a model. You can initialize it on startup to avoid the error, or create another variable that forces the view to wait on the variable.

import { Component, OnInit, OnDestroy } from '@angular/core';
import { CategoryService } from 'src/app/category.service';
import { ProductService } from 'src/app/product.service';
import { Router, ActivatedRoute } from '@angular/router';
import { take } from 'rxjs/operators';

@Component({
  selector: 'app-product-form',
  templateUrl: './product-form.component.html',
  styleUrls: ['./product-form.component.css']
})
export class ProductFormComponent implements OnInit {

  categories$;
  product;
initialized : boolean = false

  constructor(
    categoryService: CategoryService,
    private route: ActivatedRoute,
    private productService: ProductService,
    private router: Router) {
    this.categories$ = categoryService.getCategories();

    let id = this.route.snapshot.paramMap.get('id');
     if (id) this.productService.get(id).snapshotChanges().pipe(take(1))
.subscribe(p => {
this.initialized = true
this.product = p

}); }

  save(product) {
    this.productService.create(product);
    this.router.navigate(["/admin/products"]);
  }

  ngOnInit() {
  }

}

Then the html would look like this :

<div class="form-group">
            <label for="title">Title</label>
            <input #title="ngModel" *ngIf="initialized" [(ngModel)]="product.title"
name="title" id="title" type="text" class="form-control" required>
            <div class="alert alert-danger" *ngIf="title.touched && title.invalid">
              Title is required
            </div>
          </div>

You can play around with the variable to create a loading indicator of your liking

Kisinga
  • 1,640
  • 18
  • 27
0

You can simply add *ngIf="product" on outer container. It will display value only when prodcut object is initialized.

paddy
  • 21
  • 3
0

It has an easy solution. The error is displayed because while initializing the html component, product is not ready ( as api call takes some time to return data), so it is undefined. Thus use ?. as shown below in html component.



   <div class="form-group">
            <label for="title">Title</label>
            <input #title="ngModel" [(ngModel)]="product?.title" <== error here
name="title" id="title" type="text" class="form-control" required>
            <div class="alert alert-danger" *ngIf="title.touched && title.invalid">
              Title is required
            </div>
          </div>

    ````
RameshD
  • 912
  • 7
  • 6