Angular 5-保留数据并避免后续的API调用

时间:2018-07-24 11:55:24

标签: angular rxjs angular5

“ @ angular / core”:“ ^ 5.2.0”

“ rxjs”:“ ^ 5.5.6”

我对angular框架不熟悉,目前正在使用它来创建可注入服务类,该类将进行WebAPI调用以从后端数据库中提取数据。

我想保留或避免来自使用此服务类的组件的后续调用。因为它的相同数据一次又一次被调用。

我觉得我犯了一个很小的错误,无法抓住它。我想避免第二次通话的往返。 在第一次调用期间,我想将数据保留在_dyndata对象中。但是以某种方式,对于每个服务调用,它都在打印console.log('Fresh call')语句。

请提出我在哪里犯错。我想使用角度变化检测的内置功能。

我的服务类别如下:-

@Injectable()

export class DynamicDataService {

    private _dyndata: DynamicContent[];

    constructor(private _httpObj: HttpClient, private http: Http) {
    }

    headers = new HttpHeaders({
        'Content-Type': 'application/json'
    });

    getdynamiccontent(): Observable<DynamicContent[]> {

        if (this._dyndata) {
            console.log('data exists!')
            return of(this._dyndata);
        }

        console.log('Fresh call');

        return this._httpObj.get<DynamicContent[]>(environment.apiHost + 'DynamiContentforPage')
            .pipe(tap(data => this._dyndata = data)
            ); 
    }

使用该服务类的组件如下所示:-

  import { Component, ElementRef, Input, Renderer, OnInit, Inject, DoCheck } from '@angular/core';
    import { DynamicDataService } from './dynamicdata.service';
    import 'rxjs/add/operator/filter';
    import { DynamicContent } from '../interfaces/IDynamicContent';

@Component({
    selector: 'CMSBlock',
    template: `<div [innerHTML]="_dynmarkup | keepHtml"></div>`,
    providers: [DynamicDataService]
})

export class DynamicDataComponent {
    public markupcollection: DynamicContent[];
    public savedmarkup: DynamicContent[];
    @Input('key') key: string;
    public _dynmarkup: any = '';

    constructor(private dynamicdataService: DynamicDataService) {
        console.log('cstor :: dynamicdata comp');
        this.getCMSData();
    }

    public getCMSData() {
        this.dynamicdataService.getdynamiccontent()
            .subscribe(px => {
                this.setUsersArray(px);
                console.log(this.markupcollection);
            });

    }

    setUsersArray(data) {
        this.savedmarkup = data;
        if (this.savedmarkup != undefined) {
            var item = this.savedmarkup.find(fx => fx.Key == this.key);
            this._dynmarkup = item.Text

        }
    }

}

3 个答案:

答案 0 :(得分:1)

您遇到了一个非常有趣的案例。

解决问题的简短答案,就是从组件提供商中删除 DynamicDataService,并在更合适的位置包含,例如root

因此,现在让我们深入探讨问题,当按角度使用服务时,我们将它们包括在我们要在其中使用它们的模块/组件(在您的情况下为CMSBlock)的提供者中,这样一来,您就可以使用该服务,并且可以调用API并加载所需的数据。但是据我们所知,服务就是这样,一旦提供就可以访问大厅应用,因此人们可以问自己一个问题,当我们提供具有相同令牌(名称)的多个服务,或相同服务多次(与提供具有相同名称的不同服务具有相似的效果)。答案是,最后一个获胜,或者换句话说,最后提供的服务将覆盖该服务提供者的功能{{ 1}}。

在您使用多次 token组件的情况下,您要提供/初始化{strong>两次 CMSBlock,这意味着每次调用服务时,DynamicDataService处于初始状态,在您的情况下为,这要归功于“新呼叫”。

解决此问题的最佳方法是在关闭父模块(如果您在延迟加载的模块中使用该服务)或 _dyndata中提供该服务模块(如果您正在使用它急切加载)。

编辑:

第二个调用也可能在第一个调用完成之前发生,因此,对可用root的检查将失败。

这是一个现场演示,介绍了我所谈论的内容:CodeSandbox

答案 1 :(得分:1)

从对该问题的评论看来,您正在实例化该组件的两个实例,并且它们都在完成一个请求之前就开始了请求。

因此,要解决此问题,您需要重组服务如何处理请求。我建议在服务上使用BehaviorSubject来提供详细信息,如下所示:

@Injectable()
export class DynamicDataService {

    private _dyndata: BehaviorSubject<DynamicContent[]> = new BehaviourSubject<DynamicContent[]>(null);
    private _requested: boolean = false;

    constructor(private _httpObj: HttpClient, private http: Http) {
    }

    headers = new HttpHeaders({
        'Content-Type': 'application/json'
    });

    getdynamiccontent(): Observable<DynamicContent[]> {
        if (!this._requested) {
            this._requested = true;
            console.log('Fresh call');
            this._httpObj.get<DynamicContent[]>(environment.apiHost + 'DynamiContentforPage')
            .subscribe(data => this._dyndata.next(data));
        } else {
            console.log('Request already made');
        }
        return this._dyndata.asObservable().pipe(filter((e) => e != null), take(1));
    }
}

答案 2 :(得分:1)

您看到的问题是,在很短的时间内,您可能有多个组件调用this.dynamicdataService.getdynamiccontent(),同时又创建了多个HTTP请求,然后它们中的任何一个都完成,并将任何值设置为this._dyndata

使用RxJS的最佳解决方案是使用publishReplay(1)take(1)运算符:

private cachedRequest = this._httpObj.get(...).pipe(
  shareReplay(1),
  take(1),
)

getdynamiccontent() {
  return this.cachedRequest;
}

您甚至不需要使用任何中间变量来存储数据。 shareReplay运算符始终仅保留对this._httpObj.get(...)的一个订阅,而take(1)确保在响应准备就绪时正确完成链接。多亏了take(1)缓存在shareReplay(1)中的IsGrouped,任何后续的订阅者都将只获得一个值。

实时演示:https://stackblitz.com/edit/rxjs6-demo-zanygw?file=index.ts