in my angular project based on firebase and firebase i had this form , and when i try to edit a product ,i could access the id of each product but i can’t access and fill the form by other data (title,price,category…)
here is my code :
product-form.html
<div class="row">
<div class="col-md-6">
<form (ngSubmit)="save(f.value)" #f="ngForm">
<div class="form-group">
<label for="id">Id</label>
<input [(ngModel)]="product.id" name="id" type="number" class="form-control" id="id" required #id="ngModel">
<div class="alert alert-danger" *ngIf="id.touched && id.invalid">The id is required !</div>
</div>
<div class="form-group">
<label for="title">Title</label>
<input [(ngModel)]="product.title" name="title" type="text" class="form-control" id="title" required #title="ngModel">
<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">
<input type="number" class="form-control" id="price" [(ngModel)]="product.price" #price="ngModel" name="price" required min="0">
<span class="input-group-text">$</span>
</div>
<div class="alert alert-danger" *ngIf="price.touched && price.invalid">
<div *ngIf="price.errors?.['required']">"price is required !"</div>
<div *ngIf="price.errors?.['pattern']">price must be a number</div>
<div *ngIf="price.errors?.['min']">price must be =>0 ! </div>
</div>
</div>
<div class="form-group">
<label for="category">Category</label>
<select class="form-control" id="category" [(ngModel)]="product.category" name="category" #category="ngModel" required>
<option value=""></option>
<option *ngFor="let category of categories$ " [value]="category.name">{{category.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 Url</label>
<div class="input-group">
<span class="input-group-text">http://</span>
<input type="text" class="form-control" id="imageUrl" #imageUrl="ngModel" [(ngModel)]="product.imageUrl" name="imageUrl" required pattern="https?://.+">
</div>
</div>
<div class="alert alert-danger" *ngIf="imageUrl.touched && imageUrl.invalid">
<div *ngIf="imageUrl.errors?.['required']">image Url is Required!</div>
<div *ngIf="imageUrl.errors?.['pattern']">image Url is invalid! must start with http:// or https://...</div>
</div>
<br>
<button class="btn btn-primary" type="submit">Save</button>
</form>
</div>
<div class="col-md-6">
<div class="card" style="width: 20rem;">
<img [src]="imageUrl.value" class="card-img-top">
<div class="card-body">
<h5 class="card-title" style="color: dodgerblue;" >{{title.value}}</h5>
<p class="card-text">{{price.value | currency:'USD':true}}</p>
</div>
</div>
</div>
</div>
<div class="toast align-items-center text-white bg-primary border-0" role="alert" aria-live="assertive" aria-atomic="true">
<div class="d-flex">
<div class="toast-body">
Product Added succefully
</div>
<button type="button" class="btn-close btn-close-white me-2 m-auto" data-bs-dismiss="toast" aria-label="Close"></button>
</div>
</div>
product-form.ts
import { Component,Inject, Input} from '@angular/core';
import { FormControl, FormGroup } from '@angular/forms';
import { CategoriesService } from '../../services/categories.service';
import { Router, RouterLink, ActivatedRoute } from '@angular/router';
import { Observable } from 'rxjs';
import { tap, take } from 'rxjs/operators';
import { Subject } from 'rxjs';
import { takeUntil } from 'rxjs/operators';
import { ChangeDetectorRef } from '@angular/core';
import { AngularFireDatabase } from '@angular/fire/compat/database';
import { ProductService } from 'src/app/services/product.service';
import { AngularFireStorage } from '@angular/fire/compat/storage';
import { AngularFirestore } from '@angular/fire/compat/firestore';
@Component({
selector: 'app-products-form',
templateUrl: './products-form.component.html',
styleUrl: './products-form.component.css'
})
export class ProductsFormComponent {
categories$:any;
products:any
product$:any;
private destroy$ = new Subject<void>();
product: any = {
id: '',
title: '',
price: '',
category:'',
imageUrl:''
};
constructor( categories: CategoriesService,private ProductService :ProductService, private router: Router,private db:AngularFireDatabase, private afs:AngularFirestore ,private route:ActivatedRoute,private cdr: ChangeDetectorRef,)
{
this.db.list('/categories').valueChanges().pipe(
tap(data => {
this.categories$ = data;
})
).subscribe(
() => {
console.log(this.categories$);
},
);
this.afs.collection('/products/').valueChanges().pipe(
tap(data => {
this.products = data;
})
).subscribe(
() => {
console.log(this.products);
},
);
const id = this.route.snapshot.paramMap.get('id');
if (id) {
console.log(id);
this.ProductService.getProductWithKey(id).pipe(takeUntil(this.destroy$))
.subscribe(productData => {
this.product = productData;
console.log(this.product)
});
}
}
ngOnInit() {
const id = this.route.snapshot.paramMap.get('id');
if (id) {
console.log(id);
this.ProductService.getProductWithKey(id).pipe(takeUntil(this.destroy$))
.subscribe(productData => {
console.log(productData);
this.product = productData;
});
}
}
save(product:any){
this.ProductService.create(product);
this.router.navigate(['/admin/products']);
console.log(product);
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
Product.service.ts
import { Injectable } from '@angular/core';
import { AngularFireDatabase } from '@angular/fire/compat/database';
import {AngularFirestore,AngularFirestoreDocument} from '@angular/fire/compat/firestore';
import { Observable } from 'rxjs';
import { tap, take,map} from 'rxjs/operators';
import { Product } from './Product';
@Injectable({
providedIn: 'root'
})
export class ProductService {
product$:any = {};
constructor(private db:AngularFireDatabase,public afs: AngularFirestore) {
}
create(product:any){
this.db.list('/products').push(product).then(()=>{console.log(`New product added with ID: ${product.id}`);});
this.afs.collection('/products').add(product).then(()=>{console.log(`New product added with ID: ${product.id}`);});
}
getAll(){
return this.afs.collection('/products');
}
getProductWithKey(id: string) {
return this.afs.doc<Product>(`/products/${id}`).snapshotChanges().pipe(
map(action => {
const data = action.payload.data() as Product;
const title = action.payload.data()?.title;
const price = action.payload.data()?.price;
const category = action.payload.data()?.category;
const imageUrl = action.payload.data()?.imageUrl;
console.log(action.payload.data()?.title);
const id = action.payload.id;
return { id,title,price,category,imageUrl};
})
);
}
}
list of products got from firestore databse
expected when click id
got only id in the form !
What i got in the console:
3
Answers
The id that you are passing to this method
getProductWithKey(id: string)
should be thedocumentId
and not the custom id field that you have in the document fields. For example, you can do the following:This should return the first product that you have created in this list. Or another way is to get all the documents in a collection, for example:
This function says that everything except id might be undefined in some case:
So I assume that you caught that case. Also, there might be issue of using undefined in forms. So it would be better to assign null instead of undefined like this for example:
Also I see you have to subscriptions in which assigns products: one in constructor and one in ngOnInit. It is usually bad practice because in asynchronous code you can’t be sure which assignement be first, so I would recommend to change it.
Either You have to pass firestore ID to the below query
This Firestore ID should be taken when getting collection DOCS.
OR if the only data in your hand is id:9
Then you should go with "where" query.
Here Docs with "id" field with "9" is filtered out and,by using LIMIT,datas filtered out is limited to only 1.