代码之家  ›  专栏  ›  技术社区  ›  MindlessRouse

路由解析程序在没有订阅的情况下不触发可观察

  •  2
  • MindlessRouse  · 技术社区  · 7 年前

    我有一条路由,在加载路由之前,它需要来自Firebase db的一些数据。感觉路由没有调用subscribe,因此请求永远不会被触发。我错过了一步吗?

    (角度5)

    我的路由器:

    {
      path: 'class/:idName',
      component: ClassComponent,
      resolve: {
        classData: ClassResolver
      }
    },
    

    我的解析器:

    @Injectable()
    export class ClassResolver implements Resolve<any> {
    
        constructor(
            private db: AngularFireDatabase
        ) {}
    
        resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
            // return 'some data'; //This worked fine
            return this.db
               .list('/')
               .valueChanges() // Returns Observable, I confirmed this. 
               //.subscribe(); // This returns a Subscriber object if I call it and I never get any data
        }
    
        // I tried this and it didnt work either
        //const list = this.db
        //        .list('/')
        //        .valueChanges();
        //console.log('list', list);  // Is a Observable
        //list.subscribe(data => {
        //    console.log('data', data); // returned data
        //    return data;
        //});
        //return list; // never gets to the component
    }
    

    我的组件:

    public idName: string;
    // Other vars
    
    constructor(
        private fb: FormBuilder,
        private route: ActivatedRoute,
        private db: AngularFireDatabase
    ) {
        // Form stuff    
    }
    
    ngOnInit() {
        // Never makes it here
        this.idName = this.route.snapshot.params.idName;
        const myclass = this.route.snapshot.data.classData;
        console.log('myclass', myclass);
    }
    

    我从来没有到达组件。它等待组件加载,但从未加载。如果我添加订阅和控制台。它返回的数据非常快,数据正确,因此它不是服务。


    呼叫后 .subscribe() 在我的解析器中,现在返回 Subscriber 对象因为我的返回签名允许 any 它返回这个 订阅人 好像是数据。现在这似乎很明显。

    我现在的问题是,为什么它不能解决我的 Observable ?

    3 回复  |  直到 5 年前
        1
  •  2
  •   Alex Peters    5 年前

    你的 resolve 函数正在返回一个 从不完成 .可观察到的确实正在发射(这可以通过添加 tap 但解决阶段不会结束(因此您的组件不会加载),直到可观察到 完成 (文档不太擅长强调这一点。)

    显然,您也不希望您的Observable完成,因为这样您就不会得到进一步的数据更新。

    最简单的修复方法是将您的可观察对象包装在 许诺 :

    async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
      return this.db.list('/').valueChanges();
    }
    

    但这并不能保证Firebase已经发出了它的初始响应,我觉得这是你在加载路径之前试图确保的。

    我能看到的唯一方法是:

    1. 确保组件在Firebase至少返回一次数据之前不会加载;和
    2. 防止两次不同的Firebase读取(一次由解析器读取,另一次由组件读取)以实现一次有效操作

    就是把你的火力基地 服务 :

    import { Injectable, OnDestroy, OnInit } from '@angular/core';
    import { AngularFireDatabase } from '@angular/fire/database';
    import { Subscription } from 'rxjs';
    import { shareReplay } from 'rxjs/operators';
    
    @Injectable({
      providedIn: 'root',
    })
    export class DataService implements OnInit, OnDestroy {
      constructor(private readonly db: AngularFireDatabase) {}
    
      /**
       * Observable to the data.
       * shareReplay so that multiple listeners don't trigger multiple reads.
       */
      public readonly data$ = this.db
        .list('/')
        .valueChanges()
        .pipe(shareReplay({ bufferSize: 1, refCount: true }));
    
      /**
       * To trigger the first read as soon as the service is initialised,
       * and to keep the subscription active for the life of the service
       * (so that as components come and go, multiple reads aren't triggered).
       */
      private subscription?: Subscription;
    
      ngOnInit(): void {
        this.subscription = this.data$.subscribe();
      }
      ngOnDestroy(): void {
        this.subscription?.unsubscribe();
      }
    }
    

    然后您的解析器将如下所示:

    async resolve(route: ActivatedRouteSnapshot): Promise<Observable<any>> {
      // ensure at least one emission has occurred
      await this.dataService.data$.pipe(take(1)).toPromise();
    
      // ...then permit the route to load
      return this.dataService.data$;
    }
    

    通过在服务中包装您的Firebase Observable,您可以 OnInit OnDestroy 生命周期挂钩,您可以使用它来确保可观察的“存在于”组件负载之间(并在一个足够的地方防止多个Firebase读取)。由于数据随后会挂起,因此后续的数据加载也会更快。最后,这仍然允许您使用解析器来确保数据在继续加载组件之前立即可用。

        2
  •  1
  •   Nolan Walker    7 年前

    您的代码看起来是正确的。您是否一直在向类路由传递参数?如果没有参数,它将无法解析,这可能是您无法实现ngOnInit函数的原因。我建议控制台也记录您的路线快照,以确保您抓取了正确的对象。我还将发布一个我得到的解决示例:

    组成部分ts

    import { Component, OnInit } from '@angular/core';
    import { ActivatedRoute } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    
    @Component({
      selector: 'app-home',
      templateUrl: './home.component.html',
      styleUrls: ['./home.component.css']
    })
    export class HomeComponent implements OnInit {
    
      public data: Observable<any>;
    
      constructor(private router: ActivatedRoute) { }
    
      ngOnInit() {
        this.data = this.router.snapshot.data.test;
      }
    }
    

    工艺路线。ts

    { path: 'home/:id', component: HomeComponent, resolve: { test: ResolverService } },
    

    ResolverService解决方案服务

    import { Injectable } from '@angular/core';
    import { Resolve } from '@angular/router';
    import { Observable } from 'rxjs/Observable';
    import 'rxjs/add/observable/of';
    
    @Injectable()
    export class ResolverService implements Resolve<Observable<any>> {
    
      constructor() { }
    
      public resolve(route: ActivateRouteSnapShot): Observable<any> {
        return Observable.of({test: 'Test Observable'});
      }
    }
    

    HTML

    {{this.data.test}}
    
        3
  •  1
  •   Matthew Marichiba    4 年前

    您只需添加 take(1) 操作符返回到分解器返回的可观察对象,以便完成。

        resolve(route: ActivatedRouteSnapshot): Observable<any> {
            return this.db.list('/').valueChanges()
                .pipe(take(1)); // <-- The Magic
        }
    
    

    @亚历克斯·彼得斯(AlexPeters)走在了正确的道路上,但你不必走得太远,甚至还得回报一个承诺。只需强制完成 取(1) Alex也注意到文档对此不是很清楚。我只是花了几个小时来调试这个问题。