Angular5双向绑定报错:TypeError: Cannot read property 'title' of null
Hey there, let's walk through the issues causing your two-way binding to fail and that frustrating error message. I can see a couple of key problems in your code—let's tackle them one by one:
1. Broken Firebase Path in ProductService
First up, your get method in ProductService has a critical typo in the database path. Right now you're concatenating /products with the product ID directly, which creates an invalid path like /productsabc123 (missing the slash between the collection and document ID). Firebase can't find the product with this wrong path, so it returns null, which triggers the error.
Fix the ProductService get method:
import { Injectable } from '@angular/core'; import { AngularFireDatabase } from 'angularfire2/database'; @Injectable() export class ProductService { constructor(private db: AngularFireDatabase) { } create(product) { return this.db.list('/products').push(product); } getAll() { return this.db.list('/products').snapshotChanges().map(categories => { return categories.map(c => ({ key: c.payload.key, ...c.payload.val() })); }); } get(productId) { // Add the missing slash between /products and the product ID return this.db.object(`/products/${productId}`).valueChanges(); } // Optional: Add an update method to handle editing existing products update(productId, product) { return this.db.object(`/products/${productId}`).update(product); } }
2. Handling Asynchronous Data & Null Values
Even with the fixed path, Firebase returns data asynchronously. When your template first loads, product might still be null (if the subscription hasn't completed yet) or could be null if the product ID doesn't exist in the database. This causes the template to throw an error when it tries to access product.title before the data is ready.
Fix 1: Use the Safe Navigation Operator in the Template
Add ?. to your ngModel binding to safely access the property only when product exists:
<form #f="ngForm" (ngSubmit)="save(f.value)"> <div class="form-group"> <label for="title">Title</label> <!-- Add ?. to safely access product.title --> <input #title="ngModel" [(ngModel)]="product?.title" name="title" id="title" type="text" class="form-control" required> <div class="alert alert-danger mt-1" *ngIf="title.touched && title.invalid"> Title is required. </div> </div> <!-- other input fields here --> </form>
Fix 2: Default to an Empty Object When Product is Null
In your component's subscription, make sure you never set product to null—fall back to an empty object instead:
import { Product } from './../../models/product'; import { CategoryService } from './../../category.service'; import { Component, OnInit } from '@angular/core'; import { ProductService } from '../../product.service'; import { Router, ActivatedRoute } from '@angular/router'; import 'rxjs/add/operator/take'; @Component({ selector: 'app-product-form', templateUrl: './product-form.component.html', styleUrls: ['./product-form.component.css'] }) export class ProductFormComponent implements OnInit { categories$; product: any = {}; // Initialize with empty object to avoid null state constructor( private router: Router, private route: ActivatedRoute, private categoryService: CategoryService, private productService: ProductService) { this.categories$ = categoryService.getCategories(); const id = this.route.snapshot.paramMap.get('id'); if (id) { this.productService.get(id) .take(1) .subscribe(p => { // If p is null (product not found), default to empty object this.product = p || {}; }); } } ngOnInit() { } // Update save method to handle both create and edit scenarios save(product) { const id = this.route.snapshot.paramMap.get('id'); if (id) { this.productService.update(id, product); } else { this.productService.create(product); } this.router.navigate(['/admin/products']); } }
Fix 3 (Optional): Hide Form Until Data Loads
If you prefer not to use the safe navigation operator, wrap your entire form in an *ngIf to only render it when product exists:
<div *ngIf="product"> <form #f="ngForm" (ngSubmit)="save(f.value)"> <!-- form content here --> </form> </div>
3. Bonus: Fixing the Console Log Issue
You might have noticed your console.log(this.product) in the constructor prints an empty object—that's because the Firebase subscription is asynchronous. The log runs before the data is returned from Firebase. Move the log inside the subscription if you want to see the loaded product:
if (id) { this.productService.get(id) .take(1) .subscribe(p => { this.product = p || {}; console.log(this.product); // Now this will log the actual product data }); }
These changes should resolve the Cannot read property 'title' of null error and get your two-way binding working correctly. Let me know if you run into any other snags!
内容的提问来源于stack exchange,提问作者Joginder Pawan Kumar

