web-dev-qa-db-fra.com

L'abonnement aux paramètres de requête Angular2 se déclenche deux fois

Essayer de gérer un scénario de connexion OAuth dans lequel, si l'utilisateur atterrit sur une page avec authorization_code dans la chaîne de requête, nous traitons le jeton et continuons ou s'ils atterrissent sur la page sans cela, nous vérifions le stockage local pour leur jeton existant , assurez-vous qu’il est toujours valide et redirigez-le ou connectez-vous en fonction de sa validité.

Le problème est que, lorsque nous vérifions l'existence du paramètre chaîne de requête authorization_code, l'abonnement est déclenché deux fois. La première fois, il est vide, la deuxième fois, il a la valeur correcte dans le dictionnaire.

app.component.ts

export class App implements OnInit {
    constructor(private _router: ActivatedRoute) {
    }

    public ngOnInit(): void {
        console.log('INIT');
        this._route.queryParams.subscribe(params => {
            console.log(params);
        });
    }
}

Ce code affiche:  output

Plunker (vous devrez le faire apparaître dans une nouvelle fenêtre et ajouter une chaîne de requête ?test=test).

Des questions

  1. Y a-t-il quelque chose que je fais de mal à le faire feu deux fois?
  2. Je ne peux pas simplement ignorer l'objet vide avec un conditionnel car c'est le scénario dans lequel nous devons valider le jeton d'authentification existant. Existe-t-il une autre façon d'aborder ce problème qui ne soit pas un hack complet?
22
lukiffer

Les observables de routeur (comme une autre réponse le mentionne) sont des sujets BehaviorSubject , ils diffèrent de RxJS Subject ou Angular 2 EventEmitter en ce qu'ils ont la valeur initiale poussée à la séquence (objet vide dans le cas de queryParams).

Généralement, la possibilité de s'inscrire avec une logique d'initialisation est souhaitable.

La valeur initiale peut être ignorée avec l'opérateur skip.

this._route.queryParams
.skip(1)
.subscribe(params => ...);

Mais une façon plus naturelle de gérer cela consiste à filtrer tous les paramètres non pertinents (la variable params initiale entre dans cette catégorie). Les valeurs en double authorization_code peuvent également être filtrées avec l'opérateur distinctUntilChanged pour éviter les appels inutiles au serveur.

this._route.queryParams
.filter(params => 'authorization_code' in params)
.map(params => params.authorization_code)
.distinctUntilChanged()
.subscribe(authCode => ...);

Notez que Angular 2 importe un nombre limité d'opérateurs RxJS (au moins map dans le cas de @angular/router). Si le paquet complet rxjs/Rx n'est pas utilisé, il peut être nécessaire d'importer des opérateurs supplémentaires (filter, distinctUntilChanged) utilisés avec import 'rxjs/add/operator/<operator_name>'.

18
estus

Le meilleur moyen de résoudre ce problème était de souscrire des événements de routeur et de traiter les paramètres de requête uniquement après que la route a été cochée sur l'état navigated

  public doSomethingWithQueryParams(): Observable<any> {
      let observer: Observer<any>;
      const observable = new Observable(obs => observer = obs);

      this.router.events.subscribe(evt => {
        // this is an injected Router instance
        if (this.router.navigated) {
          Observable.from(this.activatedRoute.queryParams)
            // some more processing here
            .subscribe(json => {
              observer.next(json);
              observer.complete();
            });
        }
      });
      return observable;
  }
5
erosb

Je suppose que c'est à dessein. 

queryParams est le BehaviorSubject

Comme vous pouvez le voir dans la docs

L'une des variantes de Subjects est BehaviorSubject, qui a un notion de "la valeur actuelle". Il stocke la dernière valeur émise à ses consommateurs, et chaque fois qu'un nouvel observateur souscrit, il le fera recevez immédiatement la "valeur actuelle" de BehaviorSubject.

En guise de solution de contournement, vous pouvez utiliser l'opérateur debounceTime comme suit:

import 'rxjs/add/operator/debounceTime';

this._route.queryParams
  .debounceTime(200)
  .subscribe(params => {
    console.log(params);
  });
3
yurzui

Utilisez simplement Location class pour obtenir l'URL initiale, UrlSerializer class pour analyser l'URL, UrlTree pour obtenir les paramètres de requête.

0
kemsky

si vous allez sur ce lien https://dev-hubs.github.io/ReactiveXHub/#/operators/conditional/skipUntil

1) Copier Coller _ ce code dans l'éditeur de code.

/* since queryParams is a BehaviorSubject */
var queryParams = new Rx.BehaviorSubject();//this will AUTOMATICALLY alert 'undefined'

var subscription = queryParams.subscribe(
    function (x) {
        alert(x);
    },
    function (err) {
        alert(err);
    },
    function () {
        alert('Completed');
    });
queryParams.onNext('yay');//this will cause to alert 'yay'

2) Appuyez sur le bouton Run

Vous verrez que vous serez alerté deux fois, un directement sur abonnement et un deuxième bcz de la dernière ligne.

Le résultat actuel n’est pas faux, c’est la philosophie qui sous-tend les opérateurs de Rx: «Faites bouger les choses», vous pouvez consulter cet arbre décisionnel pour voir l’opérateur recherché http://reactivex.io/documentation/operators.html#tree J'ai l'habitude d'utiliser skip (1)

0
Qaddura

Vous pouvez attendre que l'événement NavigationEnd soit terminé, puis obtenir les valeurs ou vous abonner aux modifications:

constructor(private router: Router, private route: ActivatedRoute) { }

    public ngOnInit(): void {
        console.log('INIT');
        this.router.events
         .subscribe((event) => {
           if (event instanceof NavigationEnd) {

             // Get a good value
             let initialParams = this.route.snapshot.queryParams; 
             console.log(initialParams);

             // or subscribe for more changes
             this.router.queryParams.subscribe(params => { 
               console.log(params);
             });

           }
       }); 

    }
0
zarpilla