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
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>'
.
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;
}
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.route.queryParams.subscribe(params => {
console.log(params);
});
}
});
}
Just in case someone is looking for a solution to this, I found a workaround that works pretty well. The solution I came up with involves only doing the subscription on the NavigationEnd
event of the Router instance.
import { ActivatedRoute, Router, NavigationEnd } from '@angular/router';
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent {
constructor(
private route: ActivatedRoute,
private router: Router
) {}
ngOnInit() {
this.router.events
.subscribe(e => {
if (e.constructor.name === 'NavigationEnd' && this.router.navigated) {
this.route.queryParams
.subscribe(params => {
// do something
})
.unsubscribe();
}
});
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With