Access the selected object of *ngFor into .ts

Basically, i have a system that stores information into a firebase database. Im marking some of this information with ‘favorite’ and storing with Storage, and to do this im passing each ‘key’ of my stored data to a function that will check is this key is ‘isFavorite’ or not.

My objective: Create a page that will show only the data that is mark as ‘isFavorite’

My problem: I cant access the ‘prod.key’ of the *ngFor=“let prod of prodsList$ | async” into the .ts file to check if he’s mark as ‘isFavorite’ or not.

favoritos.html

<ion-header>
    <ion-navbar color="amarelo">
      
      <ion-buttons left>
        <button ion-button icon-only menuToggle>
          <ion-icon name="menu"></ion-icon>
        </button>
      </ion-buttons>

      <ion-title >
          Favoritos
      </ion-title>

    </ion-navbar>
  </ion-header>


<ion-content class="cards-bg">
  <ion-card *ngFor="let prod of prodsList$ | async" detail-push navPush="AudioPlayerPage"[navParams]="{prod:prod}">
    <ion-card-content *ngIf="isFavorite">
      <ion-card-title>
        {{prod.titulo}} 
      </ion-card-title>
      <p>
        {{prod.descricao}}
      </p>
    </ion-card-content>

  </ion-card>
</ion-content>

favoritos.ts

import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Producao } from '../../models/producao';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import { ProducoesProvider } from '../../providers/producoes/producoes'
import { FavoriteProvider } from './../../providers/favorite/favorite';
import { AngularFireDatabase } from '@angular/fire/database';

@IonicPage()
@Component({
  selector: 'page-favoritos',
  templateUrl: 'favoritos.html'
})
export class FavoritosPage {

  isFavorite = false;
  prodsList$: Observable<Producao[]>;
  prod: Producao;

  constructor(public navCtrl: NavController,  public favoriteProvider: FavoriteProvider, public navParams: NavParams, 
    public producoesProvider: ProducoesProvider, private db: AngularFireDatabase) {
      
      this.prod = this.navParams.get('prod');
      console.log("A key é "+this.prod.key);
      this.favoriteProvider.isFavorite(this.prod.key).then(isFav => {
        this.isFavorite = isFav;
      })

      this.prodsList$ = this.producoesProvider.getProdsList()
        .snapshotChanges()
        .pipe(
          map(changes => {
            return changes.map(c => ({
              key: c.payload.key, ...c.payload.val()
        }))
      })
    );

  }

    showAllProds() {
      this.prodsList$ = this.producoesProvider.getProdsList()
        .snapshotChanges()
        .pipe(
          map(changes => {
        return changes.map (c => ({
          key: c.payload.key, ...c.payload.val()
        }))
      })
    );
    }

}

Make sure you have fill in getProdsList before showing it, use “console.log()” to check if it is empty.

Thank you for the quick answer, but this part is working normally. The real problem is how to pass the prod object (to access the prod.key) to the .ts file to check if he’s marked with Favorite and change the variable isFavorite (then the *ngIf will check that and show (or not) the card)

This is a very confusing page. On one hand, it appears to be a list of products. Yet since it is being passed a specific product in navParams, it appears to be concentrating on a single product.

Which is it?

You mean that part where i’m pushing a new page and passing the params? Thats working fine. Its a list of objects (actually is a model) and im passing one specific object from that list.

This is my producao.ts (that works like a model). I also have a provider that makes all this works. But these parts are working fine

export interface Producao {
    key?: string;
    titulo: string;
    descricao: string;
    url: string;
}

Im sorry if its confuse :frowning:

The list/detail pattern is common. The user chooses an item from the list presented in the list page, and moves to the detail page, which concentrates only on a single item.

The reason I find your design confusing is that detail pages do not need to care about anything outside the item they are focusing on. The presence of showAllProds() in a detail page is unusual, as is the existence of prodsList$.

This may seem trivial to you, but it is crucial for determining the proper place to put isFavorite.

The part of the showAllProds() i understand. But you are saying about detail page, but the difference is that this page is one of the tabs, no other page is pushing her, so i cant pass the data via navParams. I have another page that is the detail page, that is pushed when the user choose one of the items of the list, and that is working normal. My problem is that in the page favoritosPage, she is one of the tabs and no one is pushing her and she needs to shows only the items that are marked as isFavorite

could add from the element detail to the list of favorites using navparams pass the item to detail and from there it can be marked, or also use a radio-button to assign the value and then push to the list of favorites that I imagine form part of your local database

You say you can’t pass the data via navParams, but you are doing exactly that. This is why I am confused.

Ohhhh okay. Thats is not working, that was something that i was trying with no success

OK, so I’m going to pretend it doesn’t exist and instead listen to you when you say:

In my world, pages are queens. We do not ask queens to shop for their own groceries and cook their own meals (unless they enjoy doing these things, of course).

In more practical terms, I write providers so that they deliver exactly the data that pages want. In this case, that seems to be “a list of favorite items”.

You could do this either in FavoriteProvider or ProducoesProvider, whichever feels more natural to you. I’m going to do it in ProducoesProvider.

export class ProducoesProvider {
  constructor(..., _favs: FavoriteProvider) {}
  allProducts(): Observable<Producao[]> {
    // do whatever existing `getProdsList()` does
  }

  favoriteProducts(): Observable<Producao[]> {
    // this is fairly deep magic, so will comment extensively
    return this.allProducts().pipe(
      // convert flat array of all products to an Observable
      // that emits one at a time, change over to watching that
      switchMap(ap => from(ap)),
      // now turn each product into a stream that will emit
      // itself only if it is a favorite
      mergeMap(p => {
        // since the favorite check is a Promise, convert that 
        // to an Observable, and kick off another stream
        return from(this._favs.isFavorite(p)).pipe(
          // the filter operator will abort the stream if whatever
          // isFavorite eventually returned isn't truthy
          filter(Boolean),
          // ok, we're a favorite, but all we have at this point is
          // 'true', which isn't much help, because we want the
          // product instead. enter the mapTo operator
          mapTo(p)
        );
      }),
      // we're almost there. now we have a stream of favorite
      // products. if you're using AsyncPipe in your template,
      // this might be sufficient, but we've declared that we're 
      // going to return a complete array, so let's flatten our
      // stream into one
      toArray()
    );
  }
}

All right, really like your ideia, but is not working. Not sure if i implement it right (specially the “isFavorite”. Can you check to see if everything is fine?

producoes.ts (the provider)

import { Injectable } from '@angular/core';
import { AngularFireDatabase, AngularFireList } from '@angular/fire/database';
import { getAllDebugNodes } from '@angular/core/src/debug/debug_node';
import { Producao } from '../../models/producao';
import { Genero } from '../../models/genero';
import { Observable, from } from 'rxjs';
import { FavoriteProvider } from '../favorite/favorite';
import { switchMap, mergeMap, filter, mapTo, toArray  } from 'rxjs/operators';


@Injectable()
export class ProducoesProvider {

  private prodListRef = this.db.list<Producao>('prod-list');
  private geneListRef = this.db.list<Genero>('gene-list'); 

  constructor(public db: AngularFireDatabase, public _favs: FavoriteProvider) { }
  
  getProdsList() {
    return this.prodListRef;
  }

  allProducts(): Observable<Producao[]> {
    return this.prodListRef.valueChanges();
  }  

  favoriteProducts(): Observable<Producao[]> {

    return this.allProducts().pipe(
      switchMap(ap => from(ap)),
      mergeMap(p => {
        return from(this._favs.isFavorite(p)).pipe(
          filter(Boolean),
          mapTo(p)
        );
      }),
      toArray()
    );
  }  

}

favoritos.html

<ion-header>
    <ion-navbar color="amarelo">
      
      <ion-buttons left>
        <button ion-button icon-only menuToggle>
          <ion-icon name="menu"></ion-icon>
        </button>
      </ion-buttons>

      <ion-title >
          Favoritos
      </ion-title>

    </ion-navbar>
  </ion-header>


<ion-content class="cards-bg">
  <ion-card *ngFor="let prod of prodsList$ | async" detail-push navPush="AudioPlayerPage"[navParams]="{prod:prod}">
    <ion-card-content *ngIf="isFavorite">
      <ion-card-title>
        {{prod.titulo}} 
      </ion-card-title>
      <p>
        {{prod.descricao}}
      </p>
    </ion-card-content>

  </ion-card>
</ion-content>

favoritos.ts

import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Producao } from '../../models/producao';
import { Observable } from 'rxjs';
import { ProducoesProvider } from '../../providers/producoes/producoes'
import { FavoriteProvider } from './../../providers/favorite/favorite';
import { AngularFireDatabase } from '@angular/fire/database';

@IonicPage()
@Component({
  selector: 'page-favoritos',
  templateUrl: 'favoritos.html'
})
export class FavoritosPage {

  public isFavorite = false;
  prodsList$: Observable<Producao[]>;
  prod: Producao;

  constructor(public navCtrl: NavController,  public favoriteProvider: FavoriteProvider, public navParams: NavParams, 
    public producoesProvider: ProducoesProvider, private db: AngularFireDatabase) {
      

      this.prodsList$ = this.producoesProvider.favoriteProducts();

  }

}

favorite.ts (provider)

import { Injectable } from '@angular/core';
import { Storage } from '@ionic/storage';
 
const STORAGE_KEY = 'favoriteProds';
 
@Injectable()
export class FavoriteProvider {
 
  constructor(public storage: Storage) { }
 
  isFavorite(prodId) {
    return this.getAllFavoriteProds().then(result => {
      return result && result.indexOf(prodId) !== -1;
    });
  }
 
  favoriteProd(prodId) {
    return this.getAllFavoriteProds().then(result => {
      if (result) {
        result.push(prodId);
        return this.storage.set(STORAGE_KEY, result);
      } else {
        return this.storage.set(STORAGE_KEY, [prodId]);
      }
    });
  }
 
  unfavoriteProd(prodId) {
    return this.getAllFavoriteProds().then(result => {
      if (result) {
        var index = result.indexOf(prodId);
        result.splice(index, 1);
        return this.storage.set(STORAGE_KEY, result);
      }
    });
  }
 
  getAllFavoriteProds() {
    return this.storage.get(STORAGE_KEY);
  }
 
}

The code is running with no error, but the favorites are not showing

A couple of things.

First, I see you’re importing ionic-angular, not @ionic/angular, so I think that means you’ve still got rxjs 5.x. The testbed that I worked that code out in is using rxjs 6, so perhaps the second from needs to be fromPromise. I know that fromPromise won’t work with rxjs6, but I’m not sure if from(<promise>) does work with rxjs5.

Secondly, although the chances that it’s causing this problem are slight, you need to make sure that Storage.ready() has fired before you try to read from it. More importantly, don’t use Storage for in-app communication. Only call the storage read to populate the list of all favorites in FavoriteProvider's constructor. Keep the result as an array in FavoriteProvider and always only refer to that. Don’t keep reading from storage.

I did everything you say, except for the from, becausa i cant import fromPromise and from seems to be working. I dont know why its not showing the results, perhabs its an html or async problem. Do you see something wrong?

favoritos.html

<ion-header>
    <ion-navbar color="amarelo">
      
      <ion-buttons left>
        <button ion-button icon-only menuToggle>
          <ion-icon name="menu"></ion-icon>
        </button>
      </ion-buttons>

      <ion-title >
          Favoritos
      </ion-title>

    </ion-navbar>
  </ion-header>


<ion-content class="cards-bg">
  <ion-card *ngFor="let fav of favoritos | async" detail-push navPush="AudioPlayerPage"[navParams]="{fav:fav}">
    <ion-card-content *ngIf="isFavorite">
      <ion-card-title>
        {{fav.titulo}} 
      </ion-card-title>
      <p>
        {{fav.descricao}}
      </p>
    </ion-card-content>

  </ion-card>
</ion-content>

favoritos.ts

import { Component, OnInit, ViewChild, ElementRef, Input } from '@angular/core';
import { IonicPage, NavController, NavParams } from 'ionic-angular';
import { Producao } from '../../models/producao';
import { Observable } from 'rxjs';
import { ProducoesProvider } from '../../providers/producoes/producoes'
import { FavoriteProvider } from './../../providers/favorite/favorite';
import { AngularFireDatabase } from '@angular/fire/database';

@IonicPage()
@Component({
  selector: 'page-favoritos',
  templateUrl: 'favoritos.html'
})
export class FavoritosPage {

  public isFavorite = false;
  public favoritos: Observable<Producao[]>;

  constructor(public navCtrl: NavController,  public favoriteProvider: FavoriteProvider, public navParams: NavParams, 
    public producoesProvider: ProducoesProvider, private db: AngularFireDatabase) {
      this.favoritos = this.producoesProvider.favList;
  }

  ionViewWillLoad(){
  }

}