【问题标题】:Prevent duplicate HTTP requests in Angular 2 API-driven tree-view在 Angular 2 API 驱动的树视图中防止重复的 HTTP 请求
【发布时间】:2016-07-31 16:04:56
【问题描述】:

我有一个 Angular 2 应用程序,它调用 JSON API 将数据加载到嵌套的 HTML 列表 (<ol>)。本质上是一个动态生成的树视图。由于来自 API 的数据集最终会变得非常大,因此当用户打开一个分支时,树会逐渐填充(用户打开一个分支,使用已打开的节点的 id 调用 API,API 返回一个 JSON节点的直接子节点的提要,Angular 将返回的 JSON 绑定到一些新的 HTML <li> 元素并且树展开以显示新分支)。

您可以在Plunkr 中看到它的实际应用。它使用递归指令并且运行良好。 目前,因为我无法针对公共请求打开实际的 API,它只是调用静态 JSON 提要,因此每个节点的返回数据只是重复,但希望你能明白。

我现在要解决的问题是防止分支关闭然后重新打开时出现无关的 HTTP 调用。在阅读了HTTP client docs 之后,我希望这就像修改订阅数据服务的方法以将.distinctUntilChanged() 方法链接到app/content-list.component.ts 文件一样简单,如下所示:

getContentNodes() {
    this._contentService.getContentNodes(this._startNodeId)
        .distinctUntilChanged()
        .subscribe(
            contentNodes => this.contentNodes = contentNodes,
            error =>  this.errorMessage = <any>error
        );
}

但是,当我打开浏览器网络检查器时,每次重新打开相同的树分支时,它仍然会调用 API。

谁能建议如何解决这个问题?

非常感谢。

编辑:

我正在尝试在下面实现@Thierry Templier 的回答;缓存 API 返回的数据。所以内容服务现在是:

import {Injectable}                 from 'angular2/core';
import {Http, Response}             from 'angular2/http';
import {Headers, RequestOptions}    from 'angular2/http';
import {ContentNode}                from './content-node';
import {Observable}                 from 'rxjs/Observable';

@Injectable()
export class ContentService {
    constructor (private http: Http) {}

    private _contentNodesUrl = 'app/content.json';

    _cachedData: ContentNode[];

    getContentNodes (parentId:number) {
        if (this._cachedData) {
            return Observable.of(this._cachedData);
        } else {
            return this.http.get(this._contentNodesUrl + parentId)
                .map(res => <ContentNode[]> res.json())
                .do(
                    (data) => {
                    this._cachedData = data;
                })
                .catch(this.handleError);
        }
    }

    private handleError (error: Response) {
        console.error(error);
        return Observable.throw(error.json().error || 'Server error');
    }
}

但是,这里发生的情况是,当页面加载时,this._cachedData 返回 false,API 调用正在触发并使用返回值 (data) 填充 this._cachedData,这是正确的。但是,当在网页 UI 中打开树节点时,以下行会重复运行数千次,直到最终浏览器崩溃:

return Observable.of(this._cachedData);

对于 Angular 2,我仍然处于起步阶段,因此非常感谢任何关于为什么这对我的学习没有帮助的指示。

【问题讨论】:

    标签: angular angular2-services


    【解决方案1】:

    我会使用以下方法缓存每个节点的数据:

    getData() {
      if (this.cachedData) {
        return Observable.of(this.cachedData);
      } else {
        return this.http.get(...)
              .map(res => res.json())
              .do((data) => {
                this.cachedData = data;
              });
      }
    }
    

    distinctUntilChanged 的“问题”是您需要使用相同的数据流。但您的情况并非如此,因为您对不同的数据流执行了多个请求...

    查看这个问题了解更多详情:

    【讨论】:

    • 谢谢@Thierry。澄清一下,这应该在服务还是组件中?
    • 不客气!是的,完全在您的 getContentNodes 方法中的服务中。
    • 嗯,我不确定我是否正确地实现了这个,因为它在本地编译没有错误,但是当我在 UI 中打开一个树节点时挂起,然后浏览器崩溃。与 Plunkr 相同:plnkr.co/edit/Bi7buRtzczoiSyLPKoOh?p=preview
    • 您应该为 _cachedData 使用一个对象,其中包含每个父 ID 的节点列表。否则,您还可以从静态资源中获取相同的数据。我相应地更新了你的 plunkr:plnkr.co/edit/fB222yP83B4DXS2MSiMr?p=preview。似乎更好;-)
    • 啊,太棒了,谢谢蒂埃里!现在它是有道理的,但我怀疑我是否已经到了那里:) 我所做的唯一调整是将新对象转换为类型any(即将cachedData: {string: ContentNode[]} = {};更改为cachedData: {string: ContentNode[]} = &lt;any&gt;{};)以防止错误我的 IDE(Visual Studio 代码)。现在似乎工作得很好! :)
    【解决方案2】:

    我想我参加聚会有点晚了,但也许这会对某人有所帮助。 一个好主意是同时缓存正在进行的请求。所以创建两个缓存:

    • observableCache
    • dataCache

    当我们动态创建components(即在*ngFor)并且这些组件中的每一个在其ngInit生命周期阶段进行数据调用时,一个用例可能是这样的情况。 这样,即使第一个请求没有完成,应用程序也不会再次请求相同的数据,而是订阅现有的缓存 Observable。

    getData() {
      if (this.cachedData) {
        console.log('data already available');
        // if `cachedData` is available return it as `Observable`
        return Observable.of(this.cachedData);
      }
      else if (this.observableCache) {
        console.log('request pending');
        // if `this.observableCache` is set then the request is in progress
        // return the `Observable` for the ongoing request
        return this.observableCache;
      }
      else {
        console.log('send new request');
        // set observableCache as new Observable returned by fetchData method
        this.observableCache = this.fetchData();
      }
      return this.observableCache;
    }
    
    fetchData() {
      const url = String('your endpoint url');
    
      return this.http.get(url)
       .map(rawData => {
           // when the cached data is available we don't need the `Observable` reference
           this.observableCache = null;
           // set cachedData
           this.dataCache = new Data(rawData);
    
           return this.dataCache;
       })
       .share(); // critical, for multiple subscribings to the same observable
    }
    

    此处为完整示例:https://medium.com/@garfunkel61/https-medium-com-garfunkel61-angular-data-provider-services-in-memory-data-cache-c495a0ac35da

    【讨论】:

      猜你喜欢
      • 2018-11-24
      • 2019-01-19
      • 1970-01-01
      • 1970-01-01
      • 2018-03-16
      • 2017-12-19
      • 2016-03-23
      • 1970-01-01
      • 2016-06-24
      相关资源
      最近更新 更多