web-dev-qa-db-ja.com

Angular2クエリパラメータサブスクリプションが2回起動する

OAuthログインシナリオを処理しようとして、ユーザーがクエリ文字列にauthorization_codeを含むページに到達した場合、トークンを処理して続行しますまたは彼らがそれなしでページに着地した場合、既存のトークンのローカルストレージをチェックし、それがまだ有効であることを確認し、その有効性に基づいてログインにリダイレクトするか続行します。

問題は、authorization_codeクエリ文字列パラメーターの存在を確認している場所で、サブスクリプションが2回起動されることです。初めて空になると、2回目は辞書に正しい値を持ちます。

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回発射させるために私が間違っていることはありますか?
  2. 条件付きの空のオブジェクトを無視することはできません。これは、既存の認証トークンを検証する必要があるシナリオだからです。完全なハックではない、これにアプローチする別の方法はありますか?
23
lukiffer

ルーターのオブザーバブル(別の回答の言及として) BehaviorSubject件名 、通常のRxJS SubjectまたはAngular 2 EventEmitterは、シーケンスにプッシュされた初期値を持っているという点(queryParamsの場合は空のオブジェクト)。

一般に、初期化ロジックでサブスクライブする可能性が望まれます。

初期値は、skip演算子を使用してスキップできます。

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

しかし、これを処理するより自然な方法は、関係のないパラメーターをすべて除外することです(最初のparamsはこのカテゴリーに分類されます)。重複する_authorization_code_値は、distinctUntilChanged演算子でフィルタリングして、バックエンドへの不要な呼び出しを回避することもできます。

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

Angular 2は、限られた量のRxJS演算子(_@angular/router_の場合は少なくともmap)をインポートします。完全な_rxjs/Rx_バンドルではない場合]使用される場合、_import 'rxjs/add/operator/<operator_name>'_で使用されている追加の演算子(filterdistinctUntilChanged)をインポートする必要がある場合があります。

19
Estus Flask

これを克服する最良の方法は、ルーターイベントをサブスクライブし、ルートが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;
  }
6
erosb

これは設計によるものだと思います。

queryParamsBehaviorSubject

docs でわかるように

サブジェクトのバリアントの1つにBehaviorSubjectがあり、これには「現在の値」という概念があります。コンシューマーに発行された最新の値を保存し、新しいオブザーバーがサブスクライブするたびに、すぐにBehaviorSubjectから「現在の値」を受け取ります。

回避策として、次のようにdebounceTime演算子を使用できます。

import 'rxjs/add/operator/debounceTime';

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

NavigationEndイベントが完了するまで待機してから、値を取得するか、変更をサブスクライブできます。

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

           }
       }); 

    }
1
zarpilla

Locationクラスを使用して初期URLを取得し、UrlSerializerクラスを使用してURLを解析し、UrlTreeを使用してクエリパラメータを取得します。

0
kemsky

NgAfterViewInitメソッドにサブスクリプションコードを配置し、

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