【问题标题】:Angular 5 - persist the data and avoid subsequent API callsAngular 5 - 持久化数据并避免后续 API 调用
【发布时间】:2018-07-25 15:56:18
【问题描述】:

"@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

        }
    }

}

【问题讨论】:

  • tap() 函数有什么用?
  • 您是否在点击回调中设置了断点以确保它正确通过?
  • 是的,它工作正常。
  • 你一定是在某处做错了,它在这里工作正常angular5-http-client-service-demo-neerrq.stackblitz.io。您是在 SAME 模块中再次调用该方法吗?
  • 啊——那将是时间问题。在实例化第二个时,第一个还没有完成加载数据。因此,当第二个请求开始时,_dyndata 为空,因此它会触发另一个请求。为避免这种情况,您需要在将这些组件包含到页面之前进行重组以加载数据。

标签: angular rxjs angular5


【解决方案1】:

从问题的 cmets 看来,您正在实例化组件的两个实例,并且它们都在完成请求之前开始请求。

因此,要解决此问题,您需要重新构建服务处理请求的方式。我建议在服务上使用 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));
    }
}

【讨论】:

  • 最后一行出现错误。 return this._dyndata.pipe(filter((e) => e != null).take(1));
  • “void”类型的“this”上下文不可分配给“Observable”类型的方法“this”
  • 它很奇怪,仍然在过滤器 fn 下方给我红线
  • 你导入observable filter算子了吗?
  • 从'rxjs/operator/filter'导入{过滤器};
【解决方案2】:

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

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

所以现在让我们深入研究这个问题,当以角度使用服务时,我们将它们包含在我们想要使用它们的模块/组件的提供者中(在你的情况下是CMSBlock),这样做您可以访问该服务,并且可以拨打您的API 并加载所需的数据。但是我们知道服务是这样的,一旦提供就可以大厅应用程序访问,所以人们可以问自己一个问题,当我们提供时会发生什么多个服务具有相同的令牌(名称),或相同的服务多次(这与提供具有相同名称的不同服务的效果相似)。答案是最后一个获胜,或者换句话说,最后一个提供的服务将覆盖作为服务提供者的函数@987654326 @。

在您使用 多次 CMSBlock 组件的情况下,您提供/初始化 两次 DynamicDataService,这意味着每次您调用服务时,_dyndata 处于初始状态,在您的情况下,它是 empty 并且由于进行了“新调用”。

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

编辑:

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

这是一个现场演示,我谈到的事情:CodeSandbox

【讨论】:

  • 我不相信这是这里的原因。我认为只有一个服务实例,但是调用了两次,因为第二次调用开始时 _dyndata 仍然为空。
  • 很可能是两者兼而有之,您对_dynata=null的看法完全正确
  • 我开始认为您对服务注册方式的模块加载方面可能是正确的。
【解决方案3】:

您看到的问题是,您可能有多个组件在很短的时间内调用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) 确保在响应准备好时正确完成链。由于缓存在shareReplay(1) 中的take(1),任何后续订阅者将只收到一个值。

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

【讨论】:

  • 我会试试这个改变,看看效果如何。
  • 哦,真漂亮:)
  • 当我尝试这种方式时,它在 take fn 中显示设计时错误。
  • return this._httpObj.get(environment.apiPartnerHost + 'DynamiContentforPage') .pipe(shareReplay(1), take(1), tap(data => this._dyndata =数据));
  • 请提出建议。
猜你喜欢
  • 2019-10-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-12-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2013-02-16
相关资源
最近更新 更多