Cannot read properties of undefined (reading ‘title’) 2

Dear Mr. Jason & everyone

I am getting this error

the add new-product page is not working
I appreciate any help, I am stuck here for two weeks…


As usual going to need to see the code for the relevant HTML and Typescript files to see what might be going wrong.

by all means

// product-form.component.ts

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ActivatedRoute, Router } from '@angular/router';
import { CategoryService } from 'src/app/category.service';
import { Product } from 'src/app/models/product';
import { ProductService } from 'src/app/product.service';
import { tap } from 'rxjs/operators';
import { Observable, Subscription } from 'rxjs';
import { AngularFireList } from 'angularfire2/database';
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, OnDestroy {
  categories$!: { title: string; }[] | any;
  categories!: { title: string; }[] | any;
  subscription!: Subscription;
  subscription$!: Subscription;
  product: any;
  id;
  //product = {};

  constructor(
    private router: Router,
    private route: ActivatedRoute,
    private categoryService: CategoryService, 
    private productService: ProductService) { 
      
      /*
      this.subscription = this.categoryService.getCategories().subscribe( categories => {
        this.categories = categories;
      }); */

      this.categories$ = categoryService.getCategories();
      //this.categories$ = this.categoryService.getCategoriesValueChanges();

      /*this.subscription$ = this.categoryService.getCategoriesValueChanges().subscribe( categories => {
        this.categories$ = categories;
      }); */
      //this.categories$ = this.categoryService.getCategoriesValueChanges();

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

  }   // no need to add the private access modifier as we are not going to reference this anywhere outsde the constructor

save(product: Product){  // I added HTMLInputElement from my own
  console.log(product);
  if(this.id) this.productService.update(this.id, product);
  else  this.productService.create(product);

  this.router.navigate(['/admin/products']);
}

  ngOnInit(): void {
    this.categories = this.categoryService.getCategories()
                                        .pipe(tap(c => console.log("category =", c)));
  }

  ngOnDestroy() {
    //this.subscription.unsubscribe();
    //this.subscription$.unsubscribe();
  }

}
// product-form.component.html
<div class="row">
    <div class="col-md-6">
        <form #f="ngForm" (ngSubmit)="save(f.value)">
            <div class="form-group">
                <label for="title">Title</label>
                <input #title="ngModel" [(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>
            <div class="form-group">
                <label for="price">Price</label>
                <div class="input-group-prepend">
                    <span class="input-group-text">$</span>
                    <input #price="ngModel" [(ngModel)]="product.price" name="price" id="price" type="number" class="form-control" [min]="0" required>
                </div>
                <div class="alert alert-danger" *ngIf="price.touched && price.invalid">
                    <div *ngIf="price.errors.required">Price is required</div>
                    <div *ngIf="price.errors.min">Minimun Price should be 0 or higher</div>
                </div>
            </div>
            <div class="form-group">
                <label for="category">Category</label>
                <select #category="ngModel" [(ngModel)]="product.category" name="category" id="category" class="form-control" required>
                    <option value=""></option>
                    <option *ngFor="let c of categories$ | async" [value]="c.key">{{c.data.name}}</option>
                </select>
                <div class="alert alert-danger" *ngIf="category.touched && category.invalid">Category is required</div>
            </div>
            <div class="form-group">
                <label for="imageUrl">Image</label>
                <input #imageUrl="ngModel" [(ngModel)]="product.imageUrl" name="imageUrl" id="imageUrl" type="text" class="form-control" url required>
                <div class="alert alert-danger" *ngIf="imageUrl.touched && imageUrl.invalid">
                    <div *ngIf="imageUrl.errors.required">Image Url is required</div>
                    <div *ngIf="imageUrl.errors.url">Please enter a valid URL</div>
                </div>
            </div>
            <button class="btn btn-primary">Save</button>
        </form>
    </div>
    <div class="col-md-6">
        <div class="card" style="width: 18rem;">
            <img [src]="product.imageUrl" class="card-img-top">
            <div class="card-body">
              <h5 class="card-title">{{product.title}}</h5>
              <p class="card-text">{{product.price | currency:'USD':true }}</p>
            </div>
          </div>
    </div>
</div>
// product.service
import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from 'angularfire2/database';
import { map } from 'rxjs/operators';
import { Product } from './models/product';

@Injectable({
  providedIn: 'root'
})
export class ProductService {
  product!: Product;

  constructor(private db: AngularFireDatabase) { }

  create(product: Product){
    this.db.list('/products').push(product);
  }

  getAll() {
    return this.db.list('/products').snapshotChanges().pipe(map(actions => {
      return actions.map(a => {
        const key = a.payload.key;
        const data = a.payload.val();
        return {data, key};
      })
    }));
  }

  get(productId: string) {
    return this.db.object('/products/' + productId).valueChanges();
  }

  update(productId: string, product: Product){
    return this.db.object('/products/' + productId).update(product);
  }
}

So I think you are either seeing what happens when this if condition is false or a race condition

It could also be a simple race where product has not been assigned yet.

Either way, I think you need an ngIf to check if product is assigned a value before you try to use it in your template.

Hi,

i have the same issue. can you help me

hi,
I think this has to do with TS compiler. In “your product-form.component.ts” file the type of the property ‘product’ is any. You should try giving it the ’ Product’ type like this:

product: Product;

this way the compiler will know that title is a property of the product object.