15

I'm working on an Angular Firebase Project, where I need to filter my database as well get the key values. Currently I'm using valueChanges() method in my service code (inside getUnreadBooks and getFavoriteBooks methods, as shown below) to get the data and filter it. But it gives me key value as 'undefined', when I try to get key value inside my template file. I tried to go with snapshotChanges() method, but can't work around how to use it to get key values along with filtering the data . Below are my Angular FirebaseService, home.component.ts (in which I am injecting my service code) and home.component.html (template file) Code respectively:

import { Injectable } from '@angular/core';
import { AngularFireDatabase } from 'angularfire2/database';
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/map';

@Injectable()
export class FirebaseService {

  books: Observable<any[]>;
  unreadBooks;
  favoriteBooks;

  constructor(private db: AngularFireDatabase) {}

  getBooks(){
        this.books = this.db.list('/books').valueChanges() as Observable<any[]>;
        return this.books;
    }       

  getFavoriteBooks(){
    this.favoriteBooks = this.db.list('/books').valueChanges() as Observable<any[]>;
    this.favoriteBooks = this.favoriteBooks.map(books => {
        const topRatedBooks = books.filter(item =>  item.rate>4);
        return topRatedBooks;
    })
    return this.favoriteBooks;
  }

  getUnreadBooks(){
    this.unreadBooks = this.db.list('/books').valueChanges() as Observable<any[]>;
    this.unreadBooks = this.unreadBooks.map(books => {
        const ub = books.filter(item => item.dateread == null);
        return ub;
    })
    return this.unreadBooks;
  }
}

Home.Component.ts file =>

import { Component, OnInit } from '@angular/core';
import { FirebaseService } from '../../services/firebase.service';

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

  //favorite Books
  favoriteBooks: any;
  unreadBooks: any;

  constructor(private firebaseService: FirebaseService) { }

  ngOnInit() {
    this.firebaseService.getFavoriteBooks()
        .subscribe(favBooks => {
            this.favoriteBooks = favBooks;
            console.log(this.favoriteBooks);
        })
    this.firebaseService.getUnreadBooks()
        .subscribe(ubBooks => {
            this.unreadBooks = ubBooks;
            console.log('Unread Books:', this.unreadBooks);
        })
  }

}

Home.component.html file =>

<mat-toolbar>
    My Top Rated Books
</mat-toolbar>
<mat-grid-list cols="3">
    <mat-grid-tile *ngFor="let book of favoriteBooks">
        <mat-card>
            <mat-card-header>
                <mat-card-title>
                    <h4>{{book.title}}</h4>
                </mat-card-title>
            </mat-card-header>
            <img mat-card-image src="{{book.imageUrl}}" alt="{{book.title}}">
            <mat-card-actions>
                <button mat-button mat-raised-button class="detailsButton" [routerLink]="['/book/'+book.$key]">
                    <i class="material-icons">visibility</i>Book Details</button>
                <button mat-button mat-raised-button class="editButton" [routerLink]="['/editbook/'+book.$key]">
                    <i class="material-icons">mode_edit</i>Edit Book</button>
            </mat-card-actions>
        </mat-card>     
    </mat-grid-tile>
</mat-grid-list>

<mat-toolbar>
    Books I have not read yet
</mat-toolbar>
<mat-grid-list cols="3">
    <mat-grid-tile *ngFor="let book of unreadBooks">
        <mat-card>
            <mat-card-header>
                <mat-card-title>
                    <h4>{{book.title}}</h4>
                </mat-card-title>
            </mat-card-header>
            <img mat-card-image src="{{book.imageUrl}}" alt="{{book.title}}">
            <mat-card-actions>
                <button mat-button mat-raised-button class="detailsButton" [routerLink]="['/book/'+book.$key]">
                    <i class="material-icons">visibility</i>Book Details</button>
                <button mat-button mat-raised-button class="editButton" [routerLink]="['/editbook/'+book.$key]">
                    <i class="material-icons">mode_edit</i>Edit Book</button>
            </mat-card-actions>
        </mat-card>     
    </mat-grid-tile>
</mat-grid-list>
user3760959
  • 457
  • 1
  • 6
  • 18
  • what you tried with `snapshotChanges()`?. check this example [app](https://github.com/angular/angularfire2/blob/master/docs/rtdb/lists.md#deleting-the-entire-list) – Hareesh Mar 06 '18 at 16:54
  • I tried snapshotChanges() in getFavoriteBooks() method ( in FirebaseService File ) in place of valueChanges() to get the key values. But can't work around how to apply filter method inside, while using snapshotChanges(). I need to get my books filtered as well get the key values of the filtered books. How could I do that? – user3760959 Mar 06 '18 at 17:38

8 Answers8

21

Declare a function to add id to your Object :

documentToDomainObject = _ => {
    const object = _.payload.doc.data();
    object.id = _.payload.doc.id;
    return object;
}

And use it in your getBooks method :

getBooks(){
  this.books = this.db.list('/books').snapshotChanges()
  .pipe(map(actions => actions.map(this.documentToDomainObject)));

  return this.books;
}
ibenjelloun
  • 7,425
  • 2
  • 29
  • 53
  • Thanks. But its giving me an error in '.pipe(map..)' part as: "Cannot find name 'map'. Did you mean 'Map' " – user3760959 Mar 06 '18 at 13:44
  • 3
    You need to import the rxjs operator map `import { map } from 'rxjs/operators/map';` – ibenjelloun Mar 06 '18 at 14:09
  • Have imported the method already. In spite of that, error message appears: "Cannot find name 'map'. Did you mean 'Map' " – user3760959 Mar 06 '18 at 17:13
  • 2
    Its definitely map and not Map. Did you add rxjs to your project ? – ibenjelloun Mar 06 '18 at 17:16
  • Thanks for getting back. Earlier I have imported the 'rxjs/add/operator/map' library, but now have imported ' map ' from 'rxjs/operators/map', as you suggested and this "cannot find 'map' " error message got disappeared. But the original problem still persists, I'm still getting the 'undefined' value for 'key'. – user3760959 Mar 06 '18 at 17:31
  • 1
    How could I work with this : getFavoriteBooks(){ this.favoriteBooks = this.db.list('/books').valueChanges() as Observable; this.favoriteBooks = this.favoriteBooks.map(books => { const topRatedBooks = books.filter(item => item.rate>4); return topRatedBooks; }) return this.favoriteBooks; }. I want to filter my books as well get key values of filtered books through this method. How could I do that? – user3760959 Mar 06 '18 at 17:32
  • 1
    getFavoriteBooks(){ return this.db.list('/books').snapshotChanges() .pipe(map(actions => actions.map(this.documentToDomainObject) .filter(item => item.rate>4))); } – ibenjelloun Mar 06 '18 at 17:59
  • For newer package versions, it seems like the import should be `import { map } from 'rxjs/operators` instead of `import { map } from 'rxjs/operators/map';` (as indicated by another answer here) – Ghadir Aug 24 '21 at 19:52
5

In my case I solved it by importing rxjs operator map, combined by .pipe

import { map } from 'rxjs/operators';

Example:

import { Component, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { AngularFirestore } from 'angularfire2/firestore';
import { IdeaService } from './app.service';

import { config } from './app.config';
import { Idea } from './app.model';
import {
  AngularFirestoreDocument,
  AngularFirestoreCollection
} from 'angularfire2/firestore';

@Component({
selector: 'app-root',
template: `
    <ul>
        <li *ngFor="let idea of ideas | async">
            <pre>{{ idea | json }}</pre>
        </li>
    </ul>
`
})
export class AppComponent {
  public ideasCollection: AngularFirestoreCollection<Idea>;
  public ideas: Observable<any[]>;

  constructor(db: AngularFirestore, private ideaService: IdeaService) {
    this.ideasCollection = db.collection<Idea>(config.collection_endpoint);
    this.ideas = this.ideasCollection.snapshotChanges().pipe(
        map(actions => {
          return actions.map(a => {
            const data = a.payload.doc.data() as Idea;
            const id = a.payload.doc.id;
            return { id, ...data };
          });
        }));
    }
}
3

You need to import the rxjs operator map import { map } from 'rxjs/operators'; // in rxjs 6 version

Dilip Das
  • 39
  • 2
  • 3
    Welcome to Stack Overflow. Can you elaborate on your answer and provide a working code example? – ggorlen Aug 11 '18 at 18:00
1

I imported this import { map } from 'rxjs/operators' and this solved my problem

Md Shadman
  • 11
  • 1
1

This is with angular 9 and it's working fine.this is set up in the service.ts and get the data from particular .ts file and just normally reader the data into template

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

particular .ts

constructor(private productService: ProductService) {
    this.products$ = this.productService.getAll();
   }

particular .html

<tr *ngFor="let product of products$ | async" >
    <td> {{ product.title }}</td>
    <td> {{ product.price }}</td>
0

this.data.list('/expenseCategories').snapshotChanges().forEach(snapshot=> {
      snapshot.forEach(keys => {
        TransactionService.expenseCategories.push(keys.payload.key);
      })
    });
Roshan Halwai
  • 552
  • 4
  • 5
0

Simple and effective solution: You will get both id and data

Add it in your service file in header part

import { map } from 'rxjs/operators';

your getBooks method :

getBooks(){
   return this.books = this.db.list('/books').snapshotChanges()
     .pipe(map(action => action
                .map(a => {
                    let obj:any = a.payload.doc.data()
                    return {
                        ...obj,
                        id: a.payload.doc.id
                    };
                })
            ));
}
Tushar Ghosh
  • 942
  • 1
  • 12
  • 18
0

From the Firebase docs, I see that you cannot get the key when using valueChanges(), you can only get it when using snapshotChanges().

Building on Neelaka's answer, if you're using Angular 12 and the latest version of firebase, if you have a form that uses the (keyup) event binding you can get the key and also filter like so:

In your product.services.ts file:

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

In admin-products.component.ts file:

export class AdminProductsComponent implements OnDestroy {

  products!: { title: string; }[] | any;
  filteredProducts!: any[];
  subscription: Subscription;


  constructor(private productService: ProductService) {
    
    this.subscription = this.productService.getAll().subscribe(products => {
      this.filteredProducts = this.products = products; 
    });
    
   }

  filter(query: string){
    
    this.filteredProducts = (query) ? 
    this.products.filter((p: { data: {title:string}, key: string }) => p.data.title.toLocaleLowerCase().includes(query.toLocaleLowerCase())) : 
    this.products;
  }

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

}

In admin-products.html


<br>
<p>
    <a routerLink="/admin/products/new" class="btn btn-primary">New Product</a>
</p>
<br>
<p>
  <input #query (keyup)="filter(query.value)" type="text" class="form-control" placeholder="Search...">
</p>
<br>
<table class="table">
    <thead>
      <tr>
        <th scope="col">Title</th>
        <th scope="col">Price</th>
        <th scope="col"></th>
        <th scope="col"></th>
      </tr>
    </thead>
    <tbody>
      <tr *ngFor="let p of filteredProducts">
        <td>{{ p.data.title }}</td>
        <td>{{ p.data.price }}</td>
        <td>
            <a [routerLink]="['/admin/products/', p.key]">Edit</a> ✍️ 
        </td>
        <td>
            <a (click)="delete(p.key)" id="delete">Delete</a>  
        </td>
      </tr>
    </tbody>
  </table>
Kingston Fortune
  • 905
  • 8
  • 17