Skip to content Skip to sidebar Skip to footer

How To Return An Observable From Service In Angular After Chaining Pipe Operations

I'm trying to chain / pipe operations and return an Observable from a Service in angular which uses angular fire. With promises I have this working Service saveDiploma(diploma: { t

Solution 1:

According to the AngularFire documentation ref.putString(..).snapshotChanges()

Emits the raw UploadTaskSnapshot as the file upload progresses.

So your problem is that .snapshotChanges() emits before the file upload is complete. concatMap gets triggered on every emit from the source not just on complete. You should use concat.

saveDiploma(diploma: { title: any; description: any; picture: any }) {
  const id = this.db.createId();
  const ref = this.storage.ref(`diplomas/${id}/original.jpg`);
  return concat(
    ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(ignoreElements()),
    defer(() => ref.getDownloadURL().pipe(
      switchMap(url => {
        console.log('url', url);
        const saved = {
          title: diploma.title,
          description: diploma.description,
          url,
          createdAt: firebase.firestore.FieldValue.serverTimestamp(),
          createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
        };
        return this.db.doc(`diplomas/${id}`).set(saved); // you can return a Promise directly
      })
    ))
  );
}

Possible alternative:

saveDiploma(diploma: { title: any; description: any; picture: any }) {
  const id = this.db.createId();
  const ref = this.storage.ref(`diplomas/${id}/original.jpg`);
  return ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(
    last(),
    switchMap(() => ref.getDownloadURL()),
    map(url => ({
      title: diploma.title,
      description: diploma.description,
      url,
      createdAt: firebase.firestore.FieldValue.serverTimestamp(),
      createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
    })),
    switchMap(saved => this.db.doc(`diplomas/${id}`).set(saved))
  );
}

Solution 2:

The problem here is that promises are by default eager. I think wrapping the from operator with the defer operator (https://rxjs.dev/api/index/function/defer) should solve your problem. So the code would look something like this:

return ref.putString(diploma.picture, 'data_url').snapshotChanges().pipe(
            concatMap(task => defer(() => {
                console.log('getDownloadURL');
                return from(task.ref.getDownloadURL());
            })),
            concatMap(url => defer(() => {
                console.log('url', url);
                const saved = {
                    title: diploma.title,
                    description: diploma.description,
                    url,
                    createdAt: firebase.firestore.FieldValue.serverTimestamp(),
                    createdBy: this.auth.auth.currentUser ? this.auth.auth.currentUser.uid : 'anonymous'
                };
                return from(this.db.doc(`diplomas/${id}`).set(saved));
            }))

The method passed to defer is evaluated as soon as it is subscribed to. ConcatMap will automatically subscribe to the inner observable as soon as there is an incoming notification from the source observable.


Post a Comment for "How To Return An Observable From Service In Angular After Chaining Pipe Operations"