Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Welcome To Ask or Share your Answers For Others

Categories

0 votes
231 views
in Technique[技术] by (71.8m points)

google cloud firestore - Angular observable pipe return array

I'm fairly new to Angular 9, and essentially I am wondering how do I return an array of objects from an async source? I currently have a firestore query in which the documents that are returned have a reference whose data I wish to render.

ngOnInit

this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(doc => {
                    const val = doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            })).subscribe(val => {
                this.links = val;
        });

HTML

<ion-item *ngFor="let link of links | async">
            <ion-avatar slot="start">
                <img [src]="getImage(link.get('profilepic'))">
            </ion-avatar>
            <ion-label>
                <h2>{{link.get('name')}}</h2>
                <p>{{link.get('bio')}}</p>
            </ion-label>
            <ion-checkbox id="{{link.id}}"></ion-checkbox>
        </ion-item>

This currently returns the error: InvalidPipeArgument: '[object Promise]' for pipe 'AsyncPipe' And when I remove the async pipe in the HTML it returns a zoneawarepromise.

EDIT

this.links = this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(async doc => {
                    const val = await doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            }));

This now works however I don't know how efficient and scalable the solution is for a large number of documents due to the ret array Note: querySnap is an object that has a built-in forEach method to loop through the array in it's docs property.


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
Welcome To Ask or Share your Answers For Others

1 Reply

0 votes
by (71.8m points)

It's generally not advisable to mix promises and observables. They do play well together, but it jumbles and confuses your code. It's also very simple to convert a promise into an observable. Most operators that create/deal with higher-order observables will do the conversion for you. Otherwise, from(promise) returns an observable as well.

Using forkJoin

In this case, forkJoin accepts an array of Promise without complaint.

forkJoin will run all the promises concurrently and return an array of responses once/if they all complete/resolve.

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  mergeMap(querySnap => forkJoin(
    querySnap.docs.map(
      doc => doc.get('otherUser').get()
    ))
  )
);

Using concat

concat can also accept promises as parameters. We use the spread operator (...) to turn an array into a list of parameters.

This is actually closer to what you achieve with promises, as you await each promise before running the next one. This will run your promises one at a time (and not concurrently, the way forkJoin does

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  mergeMap(querySnap => concat(
    ...querySnap.docs.map(
      doc => doc.get('otherUser').get()
    ))
  ),
  toArray()
);

Aside: Cleaning up your promise code

This:

this.links = this.currentUserRef.collection('links', ref => ref.where('pendingRequest', '==', false)).get()
            .pipe(map(querySnap => {
                const ret = [];
                querySnap.forEach(async doc => {
                    const val = await doc.get('otherUser').get().then(userData => {return userData});
                    ret.push(val);
                });
                return ret;
            }));

is equivalent to

this.links = this.currentUserRef.collection(
  'links', 
  ref => ref.where('pendingRequest', '==', false)
).get().pipe(
  map(querySnap => {
    const ret = [];
    querySnap.forEach(async doc => 
      ret.push(await doc.get('otherUser').get())
    );
    return ret;
  })
)

Basically, if you're using await, you don't need .then(...


与恶龙缠斗过久,自身亦成为恶龙;凝视深渊过久,深渊将回以凝视…
OGeek|极客中国-欢迎来到极客的世界,一个免费开放的程序员编程交流平台!开放,进步,分享!让技术改变生活,让极客改变未来! Welcome to OGeek Q&A Community for programmer and developer-Open, Learning and Share
Click Here to Ask a Question

...