J'ai des problèmes pour passer des appels Observables imbriqués. J'entends par là un appel à un service http qui récupère un utilisateur, puis obtient l'identifiant de l'utilisateur pour effectuer un autre appel http et finalement afficher les résultats à l'écran.
1) HTTP GET 1: obtenez l'utilisateur
2) HTTP GET 2: obtenez les préférences de l'utilisateur en passant un identifiant unique comme paramètre
Cela se traduit par le code suivant dans le composant Blah.ts
:
version 1 - ce code n'affiche rien
ngOnInit() {
this.userService.getUser()
.flatMap(u => {
this.user = u; // save the user
return Observable.of(u); // pass on the Observable
})
.flatMap(u => this.userService.getPreferences(this.user.username)) // get the preferences for this user
.map(p => {
this.preferences = p; // save the preferences
});
}
version 2 - ce code fonctionne mais me semble la mauvaise approche:
this.userService.getUser().subscribe(u => {
this.user = u;
this.userService.getPreferences(this.user.username).subscribe(prefs => {
this.preferences = prefs;
});
});
Et voici le modèle:
<h3>User</h3>
<div class="row col-md-12">
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">User details</h3>
</div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<tr>
<th>Username</th>
<th>Full Name</th>
<th>Enabled</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{user?.username}}</td>
<td>{{user?.fullName}}</td>
<td>{{user?.enabled}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- end of col 1-->
<div class="col-md-6">
<div class="panel panel-default">
<div class="panel-heading">
<h3 class="panel-title">User preferences</h3>
</div>
<div class="panel-body">
<table class="table table-condensed">
<thead>
<tr>
<th>Language</th>
<th>Locale</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{preferences?.preferences?.get('language')}}</td>
<td>{{preferences?.preferences?.get('locale')}}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<!-- end of col 2-->
</div>
<!-- end of row 1-->
Je ne pense pas qu'il soit utile d'afficher le service, qui fait simplement des appels à http get()
comme:
http.get('http://blablah/users/')
.map((response) => response.json())
Veuillez suggérer quelle est la meilleure approche de travail pour définir une chaîne d'observables.
Vous devriez lire un peu sur les opérateurs de rxjs. Vos exemples sont très détaillés et utilisent flatMap
et map
d'une manière qu'ils ne sont pas censés être utilisés. De plus, votre premier exemple ne peut pas fonctionner, car vous n'êtes pas abonné à l'Observable.
Cela fera ce dont vous avez besoin:
ngOnInit() {
this.userService.getUser()
.do(u => this.user = u) //.do just invokes the function. does not manipulate the stream, return value is ignored.
.flatMap(u => this.userService.getPreferences(u.username))
.subscribe(p => this.preferences = p);
}
Depuis rxjs 5.5, vous devez utiliser les opérateurs canalisables :
ngOnInit() {
this.userService.getUser().pipe(
tap(u => this.user = u),
flatMap(u => this.userService.getPreferences(u.username))
).subscribe(p => this.preferences = p);
}
D'accord, donc après une journée de lutte et de compilation d'informations sur Internet, voici ce que j'ai appris sur le chaînage des observables (appel des observables dans une séquence - l'un après l'autre):
Je travaille sur un site Web Angular2 (4) et ce site utilise une API Java backend pour obtenir/définir/modifier les informations dans la base de données.
Mon problème était que je devais faire deux appels API (HTTP POST) dans une séquence qui renvoie Observables (RxJS).
J'ai Operation1 et Operation2. L'opération 2 doit s'exécuter après la fin de l'opération 1.
Variant1 -> Au début, je l'ai fait l'un dans l'autre (comme les fonctions imbriquées en javascript):
this.someService.operation1(someParameters).subscribe(
resFromOp1 => {
this.someService.operation2(otherParameters).subscribe(
resFromOp2 => {
// After the two operations are done with success
this.refreshPageMyFunction()
},
errFromOp2 => {
console.log(errFromOp2);
}
);
},
errFromOp1 => {
console.log(errFromOp1);
}
);
Malgré que ce code soit légitime et fonctionne, j'ai eu l'obligation de chaîner ces observables les uns après les autres comme la façon dont cela se fait avec les fonctions asynchrones avec Promises. Une façon consiste à convertir les observables en promesses.
Une autre façon consiste à utiliser RxJS flatMap:
Variante2 -> Une autre façon est de le faire avec flatMap qui, si j'ai bien compris, est similaire à Promises alors:
this.someService.operation1(someParameters)
.flatMap(u => this.someService.operation2(otherParameters))
.subscribe(function(){
return this.refreshPageMyFunction()
},
function (error) {
console.log(error);
}
);
Variante3 -> La même chose avec les fonctions Flèche:
this.someService.operation1(someParameters)
.flatMap(() => this.someService.operation2(otherParameters))
.subscribe(() => this.refreshPageMyFunction(),
error => console.log(error)
);
Les méthodes qui renvoient Observables sont essentiellement les suivantes:
operation1(someParameters): Observable<any> {
return this.http.post('api/foo/bar', someParameters);
}
operation2(otherParameters): Observable<any> {
return this.http.post('api/some/thing', otherParameters);
}
Ressources supplémentaires et commentaires utiles:
This post approved answer by @j2L4e: https://stackoverflow.com/a/40803745/2979938
https://stackoverflow.com/a/34523396/2979938
https://stackoverflow.com/a/37777382/2979938
vous avez raison, les abonnements imbriqués sont faux ...
le flatmap est correct
cela devrait aider
https://embed.plnkr.co/mqR9jE/preview
ou lisez ce tutoriel
https://Gist.github.com/staltz/868e7e9bc2a7b8c1f754
du code ...
// responseStream: stream of JSON responses
var responseStream = requestStream
// We use flatMap instead of map to prevent this stream being a metastream - i.e. stream of streams
.flatMap(requestUrl => {
// Convert promise to stream
return Rx.Observable.fromPromise($.getJSON(requestUrl));
}).publish().refCount(); // Make responseStream a hot observable, prevents multiple API requests
// see https://Gist.github.com/staltz/868e7e9bc2a7b8c1f754#gistcomment-1255116
ici, l'URL de demande est une entrée émise à partir d'un flux différent/observable.
abonnez-vous maintenant à responseStream
Le version 1 est le meilleur et devrait fonctionner, vous avez juste oublié de vous abonner à:
ngOnInit() {
this.userService.getUser()
.flatMap(u => {
this.user = u; // save the user
return Observable.of(u); // pass on the Observable
})
.flatMap(u => this.userService.getPreferences(this.user.username)) // get the preferences for this user
.map(p => {
this.preferences = p; // save the preferences
})
.subscribe();
}