Angular2 Query Params Subscription Fires Twice

蓝咒 提交于 2020-01-11 18:46:35

问题


Trying to handle an OAuth login scenario where if the user lands on a page with authorization_code in the query string, we process the token and continue or if they land on the page without that, we check local storage for their existing token, make sure it's still valid and either redirect to login or continue, based on its validity.

The problem is that where we're checking for the existence of the authorization_code query string param, the subscription is firing twice. The first time it is empty, the second time it has the correct value in the dictionary.

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);
        });
    }
}

This code outputs:

Plunker (you'll need to pop it out into a new window and add a query string ?test=test).

Questions

  1. Is there something I'm doing wrong to make it fire twice?
  2. I can't just ignore the empty object with a conditional because that's the scenario where we need to validate the existing auth token -- is there another way of approaching this that isn't a complete hack?

回答1:


Router observables (as another answer mentions) are BehaviorSubject subjects, they differ from regular RxJS Subject or Angular 2 EventEmitter in that they have the initial value pushed to the sequence (an empty object in the case of queryParams).

Generally the possibility of subscribing with initialization logic is desirable.

The initial value can be skipped with skip operator.

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

But more natural way to handle this is to filter out all irrelevant params (initial params falls into this category). Duplicate authorization_code values can also be filtered with distinctUntilChanged operator to avoid unnecessary calls to the backend.

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

Notice that Angular 2 imports a limited amount of RxJS operators (at least map in the case of @angular/router). If full rxjs/Rx bundle isn't used, it may be necessary to import extra operators (filter, distinctUntilChanged) that are in use with import 'rxjs/add/operator/<operator_name>'.




回答2:


The best way to overcome this was subscribing router events, and processing query params only after the route is ticked to navigated state:

  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;
  }



回答3:


I guess that's by design.

queryParams is the BehaviorSubject

As you can see in the docs

One of the variants of Subjects is the BehaviorSubject, which has a notion of "the current value". It stores the latest value emitted to its consumers, and whenever a new Observer subscribes, it will immediately receive the "current value" from the BehaviorSubject.

As workaround you can use debounceTime operator as follows:

import 'rxjs/add/operator/debounceTime';

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



回答4:


You can wait until NavigationEnd event is done and then get the values or subscribe to changes:

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);
             });

           }
       }); 

    }



回答5:


Just use Location class to get initial url, UrlSerializer class to parse url, UrlTree to get query params.




回答6:


if you go to this link https://dev-hubs.github.io/ReactiveXHub/#/operators/conditional/skipUntil

1) Copy Paste this code in the code editor.

/* 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) Hit Run Button

You will see that you will alerted twice, one directly on subscription and second bcz of the last line.

The current result is not wrong, thats the philosophy behind Rx 'operators make things happen' you can lookup this decision tree to see the operator you are looking for http://reactivex.io/documentation/operators.html#tree I usually use skip(1)




回答7:


Put your subscription code on ngAfterViewInit method,

ngAfterViewInit() {
    this.route.queryParams.subscribe(params => {
      debugger;
    });
}


来源:https://stackoverflow.com/questions/39861547/angular2-query-params-subscription-fires-twice

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!