Angular2查询参数订阅引发两次

时间:2016-10-04 20:52:23

标签: angular rxjs angular2-routing

尝试处理OAuth登录方案,如果用户登陆查询字符串中包含authorization_code的网页,我们会处理该标记并继续,如果他们落在页面上我们检查本地存储的现有令牌,确保它仍然有效,并根据其有效性重定向登录或继续。

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

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. 我不能忽略具有条件的空对象,因为我们需要验证现有身份验证令牌的场景 - 是否有其他方法可以完成此任务并不完整劈?

8 个答案:

答案 0 :(得分:18)

路由器observables(作为另一个答案提及)are BehaviorSubject subjects,它们与常规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运算符(在map的情况下至少@angular/router)。如果未使用完整的rxjs/Rx捆绑包,则可能需要导入与filter一起使用的额外运算符(distinctUntilChangedimport 'rxjs/add/operator/<operator_name>')。

答案 1 :(得分:6)

解决此问题的最佳方法是订阅路由器事件,并仅在路由勾选到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;
  }

答案 2 :(得分:3)

我猜这是设计的。

queryParams BehaviorSubject

正如您在docs

中所看到的
  

主题的变体之一是BehaviorSubject,它有一个   “当前价值”的概念。它存储发出的最新值   它的消费者,每当新的Observer订阅时,它都会   立即从BehaviorSubject接收“当前值”。

作为解决方法,您可以使用debounceTime运算符,如下所示:

import 'rxjs/add/operator/debounceTime';

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

答案 3 :(得分:1)

您可以等到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);
             });

           }
       }); 

    }

答案 4 :(得分:0)

只需使用Location类获取初始网址,UrlSerializer类来解析网址,UrlTree以获取查询参数。

答案 5 :(得分: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 我通常使用skip(1)

答案 6 :(得分:0)

将订阅代码放在ngAfterViewInit方法上,

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

答案 7 :(得分:0)

以防万一有人在寻找解决方案,我发现了一种很好的解决方法。我想出的解决方案仅涉及对Router实例的NavigationEnd事件进行订阅。

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