J'ai un appel de service, dont la réponse est mise en cache dans mon service Angular comme ceci:
onClick() { this.myService.cacheMyServiceResponse().subscribe(() => { // call is finished.. }); }
La réponse de mon appel http.get sera donc mise en cache ici, dans une variable globale appelée "cache".
Le problème est que cet appel peut vraiment répondre très tardivement, nous voulions donc appeler ce point de terminaison dès que notre page se charge (lors de l'initialisation). Mais la réponse réelle, ou si notre appel est terminé (succès ou erreur), nous n'avons besoin de ces informations que lorsque l'utilisateur clique sur un bouton. ce cas je voudrais l'attendre. (donc j'ai besoin de plus qu'un simple drapeau booléen)
Donc je veux initialiser cet appel dans un ngOnInit comme ceci:
ngOnInit() { this.myService.cacheMyServiceResponse().subscribe(); }
Mais ailleurs, je besoin de savoir si l'appel est déjà terminé, sans lancer deux fois mon appel http.
public cacheMyServiceResponse(): Observable<any> { return this.appConfig.getEndpoint('myService') .pipe( switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())), tap((body: any) => { if (body) { // this endpoint can also return a 204 (No Content), where body is null this.cache = body.myData; } }), take(1), catchError(error => { this.errorService.trackError(error.status); return of(true); }) ); }
Pour le moment, le service sera appelé deux fois. Comment puis-je faire cela?
PS: je n'ai pas de gestion des erreurs exprès, je dois juste savoir si l'appel de service s'est terminé.
p>
5 Réponses :
Utilisez une variable booléenne pour vérifier l'état de la réponse réelle, ou si notre appel est terminé (succès ou erreur); Ce booléen peut ensuite être vérifié lorsque l'utilisateur clique sur un bouton ... suivant le code pour expliquer ce que je veux dire ...
callMade:boolean = false; callFinished:boolean = false; ngOnInit(){ this.callMade = true; this.myService.cacheMyServiceResponse().subscribe( dataa => { /* get and process data */} ,() => { /*this is the finally block */ this.callFinished = true; } ); } someOtherFunction(){ if (this.callMade == true && this.callFinished == false){ /* call the service again */ } }
tout d'abord, dans mon exemple, le bouton peut également être appelé dès le chargement de la page, et là je voudrais attendre, disons max. 10 secondes .. donc je ne peux pas simplement vérifier une variable .. deuxièmement, j'ai posé la question avec l'espoir que cela puisse être réalisé avec des rxjs purs ..
@akcasoy vous n'avez pas demandé cette dernière partie particulièrement clairement alors.
Désolé. mais dès que vous avez un appel de service, vous devez toujours gérer les cas où la réponse n'est pas encore là .. dans ma question, j'ai utilisé ".subscribe (()" dans la fonction de rappel onClick. Je pensais que cela le clarifiait que j'aimerais attendre la réponse
Vous pouvez utiliser ici des Resolvers
pour votre scénario. Il appellera votre méthode lorsque vous atteindrez votre itinéraire.
Exemple :
{ path: 'routeName', component: YourComponent, resolve: { items: ExampleResolver } }
Votre itinéraire:
@Injectable() export class ExampleResolver implements Resolve<any> { constructor(private apiService: APIService) {} resolve(route: ActivatedRouteSnapshot) { return this.apiService.getItems(route.params.date); } }
Il obtiendra votre réponse avant votre ngOnInit (). Vous pouvez également stocker votre réponse dans votre cache de service.
Pourquoi ne sauvegardez-vous pas l'Observable?
public cacheMyServiceResponse(): Observable<any> { if(this.cache) { return of(this.cache); else if(!this.currentCall) { this.currentCall = this.appConfig.getEndpoint('myService') .pipe( switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())), tap((body: any) => { if (body) { // this endpoint can also return a 204 (No Content), where body is null this.cache = body.myData; } }), take(1), catchError(error => { this.errorService.trackError(error.status); return of(true); }) ); } return this.currentCall; }
Je suggère d'utiliser ReplaySubject ()
et abonnez-vous à ReplaySubject ()
onClick à la place, il attendra que votre service émette des données pendant qu'il peut encore être abonné, même s'il n'a pas été abonné avant l'émission des données du service, vous ne manquerez pas les données:
onClick() { if(this.subscription){ this.subscription.unsubscribe() } this.subscription = this.yourWaitingData.subscribe((x) => { // subscribed and will wait for data to be emited from service console.log(x) }); }
Ensuite, abonnez-vous:
yourWaitingData = new ReplaySubject(); subscription; ngOnInit() { this.myService.cacheMyServiceResponse().subscribe(res => { //yourWaitingData only emit when res is return from API call this.yourWaitingData.next(res) }); }
Peut-être qu'un BehaviorSubject serait meilleur, sinon vous risquez de manquer la réponse
@Random merci d'avoir souligné, mis à jour ma solution en utilisant ReplaySubject
Utilisez shareReplay
pour créer un cache simple qui déclenche une fois la requête http et fournit sa valeur de retour à tous les abonnements ultérieurs à partir du cache.
Service
ngOnInit() { this.myService.getData().subscribe(); } onClick() { this.myService.getData().subscribe(data => console.log('data from click')); }
Vous pouvez vous abonner à this.myService.getData ()
plusieurs fois sans déclencher plusieurs http appels. Seul le premier abonnement déclenche un appel.
private cache$: Observable<any>; getData(): Observable<any> { if (!this.cache$) { this.cache$ = this.requestData().pipe(shareReplay(1)); } return this.cache$; } private requestData(): Observable<any> { return this.appConfig.getEndpoint('myService') .pipe( switchMap((endpoint: Endpoint) => this.http.get(endpoint.toUrl())), catchError(error => { this.errorService.trackError(error.status); return of(true); }) ); }
Vous pouvez utiliser des
résolveurs
ici pour votre scénario. Il appellera votre méthode lorsque vous atteindrez votre itinéraire. Exemple:@Injectable () classe d'exportation APIResolver implémente Resolve {constructeur (apiService privé: APIService) {} résoudre (route: ActivatedRouteSnapshot) {return this.apiService.getItems (route.params.date); }}
{chemin: 'items /: date', composant: ItemsComponent, résoudre: {items: APIResolver}}