2016-11-10 109 views
3

我有一個REST端點,它返回一個項目列表,最多1000個項目一次。如果有超過1000個項目,則響應的HTTP狀態爲206,並且有一個Next-Range標題,我可以在我的下一個獲取更多項目的請求中使用它。Angular 2 Http,Observables和遞歸請求

我正在的角2應用,並試圖與HttpObservable來實現這一點。我的問題是我不知道如何合併多個Observable s取決於有多少頁面的項目,並且最終返回一個Observable我的組件可以訂閱。

這裏就是我和我目前的打字稿實現了:

// NOTE: Non-working example! 

getAllItems(): Observable<any[]> { 
    // array of all items, possibly received with multiple requests 
    const allItems: any[] = []; 

    // inner function for getting a range of items 
    const getRange = (range?: string) => { 
    const headers: Headers = new Headers(); 
    if (range) { 
     headers.set('Range', range); 
    } 

    return this.http.get('http://api/endpoint', { headers }) 
     .map((res: Response) => { 
     // add all to received items 
     // (maybe not needed if the responses can be merged some other way?) 
     allItems.push.apply(allItems, res.json()); 

     // partial content 
     if (res.status === 206) { 
      const nextRange = res.headers.get('Next-Range'); 

      // get next range of items 
      return getRange(nextRange); 
     } 

     return allItems; 
     }); 
    }; 

    // get first range 
    return getRange(); 
} 

但是,這是行不通的。如果我的理解正確,則Observable將返回爲初始Observable的值,而不是項目的數組。

回答

7

您可以使用擴展運算符來實現此功能。你真正想要做的是創建一個遞歸平面圖。這正是運營商擴張的原因。

這裏是如何工作的代碼片段:

let times = true; 
// This is a mock method for your http.get call 
const httpMock =() => { 
    if(times) { 
    times = false; 
    return Rx.Observable.of({items: ["1", "2", "3"], next: true}); 
    } else { 
    return Rx.Observable.of({items: ["4", "5", "6"], next: false}); 
    } 
} 

httpMock() 
    .expand(obj => { 
    // In your case, the obj will be the response 
    // implement your logic here if the 206 http header is found 
    if(obj.next) { 
     // If you have next values, just call the http.get method again 
     // In my example it's the httpMock 
     return httpMock(); 
    } else { 
     return Rx.Observable.empty(); 
    } 
    }) 
    .map(obj => obj.items.flatMap(array => array)) 
    .reduce((acc, x) => acc.concat(x), []); 
    .subscribe((val) => console.log(val)); 

是什麼做的是模擬第一HTTP請求,具有「下一個」屬性設置爲true。這匹配你的206頭。然後,我們再次調用「next」屬性爲false。

結果是包含來自兩個請求的結果的數組。它也適用於更多的請求以及擴展運算符。

工作jsbin例如可以在這裏找到:http://jsbin.com/wowituluqu/edit?js,console

編輯:更新與HTTP調用返回從陣列的陣列,並且最終結果的工作是一個包含所有的元素形成所述陣列的單個陣列。

如果您希望因爲請求中的單獨數組仍然存在,請刪除平面圖並直接返回項目。此更新codepen: http://codepen.io/anon/pen/xRZyaZ?editors=0010#0

+0

出於某種原因,這不能正常工作。我在此添加了簡化代碼示例和更多評論:https://gist.github。com/roxeteer/76789931e82f38061b0d33d4fdc06c70 –

+0

我試圖用你的代碼更新JSBIN,這似乎按預期工作(對http嘲諷進行了小的修改)。 http://jsbin.com/nosohom/edit?js,console如果這不起作用,你能否給我提供一個錯誤的jsbin例子? – KwintenP

+0

奇怪。你的代碼看起來很相似,但它在Angular 2中的工作方式仍然不同。我通過修改訂閱者使用「完成」處理程序來修復它。看到我的答案:http://stackoverflow.com/a/40543794/587337 –

2

我得到了它與小的調整工作,KwintenP的例子:

// service.ts 

getAllItems(): Observable<any[]> { 
    const getRange = (range?: string): Observable<any> => { 
    const headers: Headers = new Headers(); 
    if (range) { 
     headers.set('Range', range); 
    } 

    return this.http.get('http://api/endpoint', { headers }); 
    }; 

    return getRange().expand((res: Response) => { 
    if (res.status === 206) { 
     const nextRange = res.headers.get('Next-Range'); 

     return getRange(nextRange); 
    } else { 
     return Observable.empty(); 
    } 
    }).map((res: Response) => res.json()); 
} 

在訂閱了該Observable分量,我不得不添加一個完整的處理程序:

// component.ts 

const temp = []; 

service.getAllItems().subscribe(
    items => { 
    // page received, push items to temp 
    temp.push.apply(temp, items); 
    }, 
    err => { 
    // handle error 
    }, 
() => { 
    // completed, expose temp to component 
    this.items = temp; 
    } 
); 
+0

你應該總是避免做這樣的事情。你在可觀察的地方使用外部狀態。這是一個很大的禁忌。如果你能給我提供一個給你錯誤的plnkr,我會很高興看到它。 – KwintenP

+0

如果端點返回一個數組數組,它​​看起來會失敗。看到我的CodePen:http://codepen.io/roxeteer/pen/vyLaEd?editors=0010#0 –

+0

這不是codepen工作嗎?我看到了9個項目的結果... – KwintenP

0

上面的答案很有用。我必須以遞歸方式使用分頁API獲取數據,並創建計算階乘的code snippet