Route Resolver没有订阅就不会触发observable

时间:2018-05-02 22:16:06

标签: javascript angular firebase observable angular5

我有一条路线在加载路线之前需要来自我的Firebase数据库的一些数据。感觉就像Route没有调用subscribe所以请求永远不会被触发。我错过了一步吗?

(Angular 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和console.out数据,它会很快返回正确的数据,所以它不是服务。

==

编辑:

在我的解析器中调用.subscribe()后,现在返回Subscriber个对象。因为我的返回签名允许any返回此Subscriber,就像它是数据一样。这似乎很明显。

现在我的问题变成为什么它不能解析我的Observable?

更新上面的标题和文字

==

解决:

我发现这个问题(https://github.com/angular/angularfire2/issues/624)显然这是我正在使用的firebase库中的一个问题。他们的观察者并没有被这些路线解雇。它是在2016年筹集的,仍然是一个问题。我认为它不会被修复。

我找到的解决方法就是这个

resolve(route: ActivatedRouteSnapshot): Observable<any> | Promise<any> | any {
    return this.db
            .list('/', ref => ref.orderByChild('idName').equalTo(route.params.idName))
            .valueChanges()
            .first();
}

2 个答案:

答案 0 :(得分:1)

您的代码看起来是正确的。你有没有把参数传递给你的班级路线?如果没有参数,它就无法解决,这可能就是您没有达到ngOnInit函数的原因。我建议控制台记录你的路线快照,以确保你抓住正确的对象。我还会发布一个我工作的解决方案示例:

<强> Component.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;
  }
}

<强> Routing.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}}

答案 1 :(得分:1)

您的resolve函数返回了一个从未完成的Observable。 Observable确实在触发(可以通过在控制台上添加tap并使用一些控制台日志来验证),但是直到Observable解析阶段才会结束(因此您的组件将不会加载) 完成。 (文档不能很好地突出显示这一点。)

很显然,您也不希望您的Observable完成,因为那样您将无法获得进一步的数据更新。

最简单的“解决方案”是将您的Observable包裹在 Promise 中:

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

但这不能保证Firebase发出了初始响应,我认为这是您在路由加载之前要确保的结果。

我能看到的唯一方法是:

  1. 确保在Firebase至少返回一次数据之前,组件不会加载;和
  2. 防止两次不同的Firebase读取(一个由解析器读取,然后由组件读取)

是将Firebase Observable包装在服务中:

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包装在服务中,您将获得OnInitOnDestroy生命周期挂钩,可使用它们来确保可观察的组件在组件加载之间“持续存在”(并防止多次读取Firebase)只要一个就足够)。因为数据然后随处可见,所以随后的数据加载也将更快。最后,这仍然使您能够使用解析器来确保在继续加载组件之前可以立即获得数据。