Route Resolver 在没有订阅的情况下不会触发 observable

Posted

技术标签:

【中文标题】Route Resolver 在没有订阅的情况下不会触发 observable【英文标题】:Route Resolver not firing observable without subscribe 【发布时间】:2018-10-13 03:15:01 【问题描述】:

我有一条路线需要在加载路线之前从我的 Firebase 数据库中获取一些数据。感觉就像 Route 没有调用 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 和 console.out 数据,它会很快返回正确的数据,所以它不是服务。


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

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

【问题讨论】:

是的,你不应该在那里订阅 observable。尝试在此处链接 do 方法并将传递给函数的数据记录到控制台,以查看 observable 上发出了什么。 【参考方案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  ,

解析服务

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

【讨论】:

resolve 函数返回一个工作正常的字符串时,我正在传递一个路由参数。我从您的 resolve 函数 return Observable.of(test: 'Test Observable'); 测试了这条线,效果也很好 一定与 Firebase 函数返回的 Observable 有关系吗? 感谢您的帮助,您的 cmets 帮助我朝着正确的方向前进。我能够找到解决方法。我更新了问题 很高兴它有帮助! ^.^ 这里的关键区别在于 Observable 是完成。解析器,如果输出一个 Observable,在该 Observable 完成之前不会解析。【参考方案2】:

你的resolve 函数返回一个永远不会完成的 Observable。 Observable 确实在触发(这可以通过在其管道中添加 tap 并使用一些控制台日志记录来验证)——但解析阶段不会结束(因此您的组件不会加载)直到 Observable 完成。 (文档并不擅长强调这一点。)

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

最简单的“修复”是将 Observable 包装在 Promise 中:

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

但这并不能保证 Firebase 已发出其初始响应,我认为这是您在路由加载之前试图确保的。

我能看到的唯一方法是:

    确保在 Firebase 至少返回一次数据之前不会加载组件;和 防止两次不同的 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 生命周期挂钩,您可以使用它们来确保 observable 在组件加载之间“继续存在”(并防止多个 Firebase 读取就足够了) )。因为数据会在周围徘徊,随后的数据加载也会更快。最后,这仍然使您能够使用解析器来确保在继续加载组件之前数据将立即可用。

【讨论】:

【参考方案3】:

您只需将 take(1) 运算符添加到解析器返回的 Observable 即可完成。

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

@AlexPeters 是在正确的轨道上,但你不必去回报承诺。只需使用take(1) 强制完成即可。 Alex 也发现文档对此不是很清楚。我刚刚花了几个小时调试同样的问题。

【讨论】:

我建议使用 Promise 包装的 Observable,以便 Observable 在路由加载后继续发出后续更新。如果不需要后续更新,那么上面完成的 Observable 就足够了。

以上是关于Route Resolver 在没有订阅的情况下不会触发 observable的主要内容,如果未能解决你的问题,请参考以下文章

Laravel 雄辩的关系在我的情况下不起作用[重复]

NullPointerException 堆栈跟踪在没有调试代理的情况下不可用

为啥 wctype.h 中的函数在没有 setlocale() 的情况下不起作用?

NoHandlerFoundException 的自定义异常处理程序在没有 @EnableWebMvc 的情况下不起作用

AJAX 功能在没有警报的情况下不起作用

iPhone - UIWebView 在没有数据连接的情况下不调用 didFailLoadWithError