【问题标题】:Angular2 Query Params Subscription Fires TwiceAngular2 查询参数订阅触发两次
【发布时间】:2016-10-04 20:52:23
【问题描述】:

尝试处理 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);
        });
    }
}

此代码输出:

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

问题

  1. 我做错了什么让它触发两次?
  2. 我不能只忽略带有条件的空对象,因为这是我们需要验证现有身份验证令牌的场景 - 是否有另一种不完全破解的方法?

【问题讨论】:

  • 一个有趣的转折......当没有提供查询字符串时,订阅只会触发一次。
  • 我以https://embed.plnkr.co/XgCauzZdTSqqokCdFWCi/?test=test 进行了测试,没有任何意外发生!?
  • @A_Singh 你测试了错误的 url,所以你只得到第一个空值
  • 我在使用location.go()时遇到了这个问题,然后在用户使用浏览器导航返回时尝试获取查询参数。
  • 将您的订阅代码放在 ngAfterViewInit 方法上,stackoverflow.com/questions/39861547/…

标签: angular rxjs angular2-routing


【解决方案1】:

Router observables(作为另一个答案提到)are BehaviorSubject subjects,它们与常规 RxJS Subject 或 Angular 2 EventEmitter 的不同之处在于它们将初始值推送到序列(@987654325 的情况下为空对象@)。

一般来说,使用初始化逻辑订阅的可能性是可取的。

初始值可以用skip操作符跳过。

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

但更自然的处理方法是过滤掉所有不相关的参数(初始params 属于此类)。也可以使用 distinctUntilChanged 运算符过滤重复的 authorization_code 值,以避免对后端的不必要调用。

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)。

【讨论】:

  • +1 用于找到问题的根源并解释问题。但答案并不能解决问题,因为没有 authorization_code 需要将其发送到另一个代码路径。
  • 关于您希望如何处理重定向的问题还不够清楚。无论如何,我相信第一个 sn-p 以 RxJS 最惯用的方式涵盖了这一点。 BehaviorSubject 会产生额外的价值,而 skip(1) 会消除它。
  • 在第一个示例中,如果没有 queryParams,则将跳过第一个“空”对象,并且永远不会调用订阅块。
  • Skip 现在应该放在管道内:.pipe(skip(1)).subscribe
【解决方案2】:

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

【讨论】:

    【解决方案3】:

    您可以等到 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.route.queryParams.subscribe(params => { 
                   console.log(params);
                 });
    
               }
           }); 
    
        }
    

    【讨论】:

      【解决方案4】:

      我猜这是设计使然。

      queryParamsBehaviorSubject

      正如您在docs 中看到的那样

      Subjects 的变体之一是 BehaviorSubject,它有一个 “当前值”的概念。它存储发出的最新值 它的消费者,并且每当有新的观察者订阅时,它将 立即从 BehaviorSubject 接收“当前值”。

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

      import 'rxjs/add/operator/debounceTime';
      
      this._route.queryParams
        .debounceTime(200)
        .subscribe(params => {
          console.log(params);
        });
      

      【讨论】:

      • 这在大多数情况下都有效,但不喜欢超过 debounceTime 超时的可能性。无论他们登陆哪个页面,都需要处理此查询参数,并且随着加载时间的不同,这并不完全有效。
      • 这不是一个好的解决方案,因为它依赖于任意时间值,这会减慢应用程序的速度,或者如果不满足时间要求则完全破坏它。
      【解决方案5】:

      以防万一有人正在寻找解决方案,我找到了一个非常有效的解决方法。我想出的解决方案只涉及对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();
              }
            });
        }
      

      【讨论】:

      • 优雅易懂的解决方案。按照建议,您可以在订阅之前将 if 语句替换为 .pipe(filter(e =&gt; e instanceof NavigationEnd))
      【解决方案6】:

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

      【讨论】:

      • 虽然这并不能解决我在使用 Observable&lt;Params&gt; 时遇到的技术问题,但它确实解决了我们的特定用例。
      【解决方案7】:

      如果你去这个链接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 'operators make things come' 背后的哲学,你可以查找这个决策树来查看你正在寻找的 operator http://reactivex.io/documentation/operators.html#tree 我通常使用skip(1)

      【讨论】:

        【解决方案8】:

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

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

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2016-08-06
          • 1970-01-01
          • 2018-02-11
          • 2018-07-07
          • 2018-09-05
          • 2019-09-22
          • 1970-01-01
          相关资源
          最近更新 更多