2016-10-04 82 views
13

尝试处理OAuth登录情景,如果用户在查询字符串中登录页面authorization_code,我们将处理该令牌并继续,如果它们没有登陆页面,我们检查本地存储的现有令牌,确保它仍然有效,并根据其有效性重定向到登录或继续。Angular2查询参数订阅触发两次

问题是,在我们检查authorization_code查询字符串param的存在的情况下,预订会触发两次。第一次它是空的,第二次它在字典中有正确的值。

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

此代码输出: output

Plunker(您需要将其弹出到一个新的窗口,并添加查询字符串?test=test)。

问题

  1. 有什么我做错了,使之火两次?
  2. 我不能忽略带有条件的空对象,因为这是我们需要验证现有身份验证令牌的场景 - 是否有另一种方法来处理这不是完整的黑客?
+0

一个有趣的转折......当未提供查询字符串,认购仅触发一次。 – lukiffer

+1

我测试了它[https://embed.plnkr.co/XgCauzZdTSqqokCdFWCi/?test=test](https://embed.plnkr.co/XgCauzZdTSqqokCdFWCi/?test=test),没有任何意外的事情发生! –

+0

@A_Singh你测试了错误的URL,所以你只能得到第一个空值 – yurzui

回答

12

路由器可观测量(如另一种答案提及)are BehaviorSubject subjects,它们与常规RxJS在于它们具有的初始值推到序列Subject或角2 EventEmitter(在queryParams的情况下的空的对象)是不同的。

一般情况下,订阅初始化逻辑的可能性是可取的。

初始值可以用skip运算符跳过。

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

但更自然地处理这种方式是过滤掉所有无关PARAMS(初始params属于这一类)。重复的authorization_code值也可以使用distinctUntilChanged运算符进行过滤以避免不必要的后端调用。

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

注意,角度2出口RxJS运营商的有限数量的(至少在map@angular/router的情况下)。如果未使用完整rxjs/Rx捆绑包,则可能需要导入与import 'rxjs/add/operator/<operator_name>'一起使用的额外运算符(filter,distinctUntilChanged)。

+0

+1以了解并解释问题的根源。但是答案没有解决这个问题,因为缺少authorization_code需要在另一个代码路径上发送它。 – lukiffer

+0

这个问题并不清楚你期望如何处理重定向。无论如何,我相信第一个代码片段覆盖了RxJS最常用的方式。 BehaviorSubject会导致额外的值并跳过(1)将其消除。 – estus

+1

在第一个示例中,如果没有queryParams,则第一个“空”对象将被跳过,并且该订阅块将永远不会被调用。 – Failpunk

2

我想这是设计。

queryParamsBehaviorSubject

正如你可以在docs

一个主题的变体是BehaviorSubject,其中有一个 概念“当前值”的看。它存储发送给其消费者的最新值 ,并且每当新Observer订阅时,它将立即从BehaviorSubject接收“当前值”。

至于解决方法,您可以使用debounceTime操作如下:

import 'rxjs/add/operator/debounceTime'; 

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

这在大部分时间都有效,但不喜欢超过debounceTime超时的可能性。无论他们登陆哪个页面,都需要处理这个查询参数,并且随着加载时间的变化,这不起作用。 – lukiffer

+0

这不是一个好的解决方案,因为它依赖任意的时间值来减慢应用程序的运行速度,或者如果时间不满足,就会完全中断它。 – Doughy

-1

只需使用Location类来获得初始URL,UrlSerializer类来解析URL,UrlTree获得查询参数。

+0

虽然这并没有解决我用'Observable '解决的技术问题,它确实解决了我们的特定用例。 – lukiffer

+1

您能举例 – TeodorKolev

+2

这是被接受的答案吗? – Riscie

0

如果你去这个链接https://dev-hubs.github.io/ReactiveXHub/#/operators/conditional/skipUntil

1)复制粘贴在代码编辑器的代码。

/* 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)打运行按钮

你会看到,你会提醒两次,一次直接订阅,最后一行的第二BCZ。

目前的结果是没有错的,那的Rx背后的哲学“运营商做的事情发生”,你可以查找这个决策树看到运营商你正在寻找http://reactivex.io/documentation/operators.html#tree 我通常使用跳过(1)

4

的克服这个最好的办法是订阅路由器事件,以及处理查询参数的路径被选中到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; 
    }