2016-07-01 35 views
5

Nuovo in Angular2 qui e chiedendo come eseguire questa richiesta asincrona sul pattern di matrice (non così sicuro sul nome del pattern).Come eseguire una richiesta HTTP asincrona all'interno di un loop/mappa Angular2 e modificare l'array del loop originale?

Quindi diciamo che uso Angular2 Http per ottenere una playlist di YouTube che contiene videoId in ciascun articolo di reso. Quindi ho bisogno di scorrere questo articolo con lo videoId e fare un'altra richiesta a YouTube per ottenere le singole informazioni sul video. Questa può essere un'altra richiesta HTTP comune, ottenere l'elenco quindi scorrere l'elenco e ottenere i dettagli di ogni singolo elemento.

let url = [YouTube API V3 list url] 
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics'; 

this.http.get(url) 
    .map(res => res.json()) 
    .subscribe(data => { 
     let newArray = data.items.map(entry => { 

      let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
             '&key=' + this.googleToken; 

      this.http.get(videoUrl) 
       .map(videoRes => videoRes.json()) 
       .subscribe(videoData => { 
        console.log(videoData); 

        //Here how to I join videoData into each item inside of data.items. 
       }); 
    }); 
}); 

Quindi il codice sopra funziona. Ma ho ancora queste due domande:

  1. Come posso far parte del videoData torna a data.items e assicurarsi che l'articolo corretto all'interno del data.items si unisce con la corretta videoData (la videoData che utilizza la entry.id.videoId del relativo articolo interno data.items) e creare quello nuovo newArray?

  2. Come faccio a sapere quando tutto è fatto e fare qualcosa in base a tutti i dati dopo che tutte le richieste asincrone sono finite?

  3. Il nuovoArray mantiene lo stesso ordine della prima richiesta HTTP. Come il primo HTTP ha colpito l'API dell'elenco di ricerca di YouTube con ordine di viewCount. Basta nidificare osservabili come sopra non manterrà l'ordine originale.

UPDATE

Con la soluzione inoabrian, posso raggiungere con successo i tre punti. Ma quando chiamo di nuovo il myvideoservice (come cambiare l'url come nuovo NextPageToken), l'oggetto - this._videoObject ha 2 osservatori. E carica di nuovo, ha 3 osservatori e così via. Ho bisogno di un modo per resettare il Soggetto per avere solo 1 osservatore quindi non avrò video duplicati. È un modo migliore per cancellare/resettare il soggetto?

risposta

2
// You need to set up a subject in your sevice 
private _videoObject = new Subject <any>(); 
videoDataAnnounced$ = this._videoObject; 


let url = [YouTube API V3 list url] 
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics'; 

this.http.get(url) 
    .map(res => res.json()) 
    .subscribe(data => { 
     this.cache = data.items; 
     data.items.forEach(entry => { 
      let videoUrl = detailUrl + '&id=' + entry.id.videoId + '&key=' + this.googleToken; 
      this.http.get(videoUrl) 
       .map(videoRes => videoRes.json()) 
       .subscribe(videoData => { 
        // This will announce that the full videoData is loaded to what ever component is listening. 
        this._videoObject.next(videoData); 
       }); 
     }); 
    }); 



// In your constructor for your component you can subscribe to wait on the data being loaded. 
public newVideos: any[] = []; 
constructor(private myServiceToLoadVideos: myvideoservice) { 
    let self = this; 
    this.myServiceToLoadVideos.videoDataAnnounced$.subscribe((video) => { 
     // Here you could loop through that 
     self.myServiceToLoadVideos.cache.map(d => { 
      if (d.id == video.id) { 
       // Here you have the old data set without the details which is (d) from the cached data in the service. 
       // And now you can create a new object with both sets of data with the video object 
       d.snippet = video.items[0].snippet; 
       d.statistics = video.items[0].statistics; 
       d.contentDetails = video.items[0].contentDetails; 
       this.posts = this.posts.concat(d); 
      } 
     }); 
     if(this.posts.length == this.cache.length){ 
      this.posts.sort(function(l,r){ 
      // if l is < r then it will return a negative number 
      // else it will return a positive number. 
      // That is how this sort function works 
      return l.viewCount - r.viewCount; 
      }); 

      // Here you will have all of the data and it will be sorted in order. 
     } 
    }); 
} 

UPDATE - - -

The Subject is an observable - http: //reactivex.io/documentation/subject.html 

    When you listen to the Subject it is not like q.all it will complete one by one until they complete. 

The cache in the service call is just to keep track of the playlist. 
Then when you receive the details in the constructor you can match them back up and keep order with the cache. 

Q: "And when subscribe to the subject as in your code, the result is when ALL the " 
next " in _videoObject finished, right?" 
A: No, when each individual finishes you will get once at a time. 

JS Ordina - https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort

+0

Puoi farlo alla fine? this.posts.sort ((L, R) => { ritorno l.viewCount - r.viewCount; }) – inoabrian

+0

ho aggiunto l'ordinamento al punto in cui i dati sarebbero complete. – inoabrian

+0

beh, ora è completato quando i post e la cache hanno le stesse dimensioni, quindi si ordina l'elenco completo – inoabrian

0

Hai già accesso alla funzione .map a entry dovresti essere in grado di inserire un altro valore su di esso utilizzando la notazione dot.in modo da fare qualcosa di simile:

let url = [YouTube API V3 list url] 
let detailUrl = 'https://www.googleapis.com/youtube/v3/videos?part=contentDetails,statistics'; 

this.http.get(url) 
    .map(res => res.json()) 
    .subscribe(data => { 
     let newArray = data.items.map(entry => { 

      let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
             '&key=' + this.googleToken; 

      this.http.get(videoUrl) 
       .map(videoRes => videoRes.json()) 
       .subscribe(videoData => { 
        console.log(videoData); 

        entry.videoData = videoData; 

        //Here how to I join videoData into each item inside of data.items. 
       }); 
    }); 
}); 
1

Assumendo tha t che ha bisogno solo dei dati modificati, una volta che tutti i sub-richieste sono completate

Questo è quello che vi serve

this.http.get(url) // original request 
     .map(res => res.json()) // extract json from response 
     .switchMap(data => { // take-over the original data and make the subsribers wait for sub-requests to complete 

     let modifiedRequest = new Subject(); // observable to to publish modified data, once all sub-requests are completed 

     let allSubRequests = data.items.map(entry => { // array of sub-requests 

      let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
         '&key=' + this.googleToken; 

      return this.http.get(videoUrl) 
      .map(videoRes => videoRes.json()) // extract json for every sub-request 
      .map(videoJson => { // modify the original 'data' object with the data in videoJson (I don't know the format, so can't tell how-to) 

       // modify your "data" here 

       return videoJson; // let the flow continue 
      }); 
     }); 

     let mergedSubRequests = Observable.range(0, 1).merge.apply(null, allSubRequests); // merge all sub-requests to know when will all are completed 

     mergedSubRequests.subscribe( // subscribe to merged observable 
      (d) => {}, // data from every sub-request, we don't need it 
      (e) => {}, // error from every sub-request, handle it as you want 
     () => { // success, called after all sub-requests are completed 
       modifiedRequest.next(data); // send out the modified data 
       modifiedRequest.complete(); // complete the temprary observable 
       } 
     ); 

     return modifiedRequest; // provide the observable with modified data, to subscribers of orginal request, they will not know the difference 
     }); 

Importazioni richiesti:

import {Observable} from 'rxjs/Observable'; 
import {Subject} from 'rxjs/Subject'; 
import 'rxjs/add/operator/map'; 
import 'rxjs/add/operator/merge'; 
import 'rxjs/add/observable/range'; 
import 'rxjs/add/operator/switchMap'; 

Spero che aiuti, non testato però :)

+0

Questa parte mi ha perso: let mergedSubRequests = Observable.range (0, 1) .merge.apply (null, allSubRequests); –

+0

Anche il tuo uso di switchmap. Potresti spiegare meglio perché usi switchmap qui? Come switchmap cancella l'osservabile per sempre, giusto? –

+0

Ho appena testato il tuo codice e non funziona per me ... Ho messo un console.log (dati) dopo switchMap e non c'è nessuna uscita dalla console. E ho messo una console.log (videoJson) prima del video di ritorno Json e anche nessun log. L'intero codice sembra bloccato da switchMap ... non ho idea di cosa stia andando storto ... –

1
let addDetail = (entry) => { 
    let videoUrl = detailUrl + '&id=' + entry.id.videoId + 
       '&key=' + this.googleToken; 

    return this.http.get(videoUrl) 
    .map(videoRes => Object.assign({videoData: videoRes.json()}, entry) 
}; 

let maxConcurrentRequest = 1; 
let source = this.http.get(url) 
    // any of mergeMap, concatMap, switchMap... will do 
    .mergeMap(res => Observable.from(res.json().items)) 
    .mergeMap(addDetail, null, maxConcurrentRequest); 
source.subscribe(/* Do anything you wish */); 

Ora ogni elemento che source emette è una voce della playlist è fusa con la sua videoData nel videoData campo (la prima domanda)

source completa quando tutto è fatto (la seconda domanda)

voi può controllare il numero di richieste simultanee massime a detailUrl impostando maxConcurrentRequest. Con maxConcurrentRequest = 1, source emetterà elementi nello stesso ordine in cui sono nella playlist. Se non ti interessa l'ordine, aumenta maxConcurrentRequest per una maggiore velocità

+0

Ho bisogno di mantenere l'ordine originale dei dati.items come l'elenco dei video è ordinato in base a viewCount. Quindi voglio lo stesso ordine dei dettagli del video. Ma non ho capito il tuo codice, dato che sono molto nuovo rispetto a ciò che è osservabile. Qual è l'oggetto.assign? E la mergeMap? –

+0

mergeMap = flatMap, vedi http://reactivex.io/documentation/operators/flatmap.html e http://reactivex.io/rxjs/class/es6/Observable.js~Observable.html#instance-method-mergeMap Object.assign è una funzione per "unire" oggetti: https://developer.mozilla.org/en/docs/Web/JavaScript/Reference/Global_Objects/Object/assign –

Problemi correlati