Search code examples
angularrxjsangular2-routing

Angular2 Query Params Subscription Fires Twice


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: output

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?

Solution

  • 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>'.