【问题标题】:Angular observables never come to completion handlerAngular observables 永远不会出现在完成处理程序中
【发布时间】:2020-09-26 22:58:13
【问题描述】:

下面是我的代码 sn-ps。我想知道为什么它永远不会遇到完成处理程序? 我想要做的是,首先调用 serviceA 以获取具有给定路由参数 ['key'] 的 objectA,然后调用 serviceB 以获取 objectB。因此,objectB 取决于 objectA 的结果,该结果取决于给定的 param['key']。

附言我正在使用 Angular 7 和 rxjs6

ngOnInit() {
    this.route.params.pipe(
      mergeMap(
        (params: Params) => {
          this.key = params.key;
          return this.serviceA.getObjectA(this.key);    // http request service to backend
        }
      ),
      mergeMap(
        (objectA: ObjectA) => {
          // do something with objectA

          return this.serviceB.getListOfObjectB();  // http request service to backend
        }
      )
    ).subscribe(
      (objectBList: ObjectB[]) => {
        for (const b of objectBList) {
          // do something with objectB
        }
        // the code execution ends here
      },
      () => {
        // error handler
      },
      () => {
        // completion handler
        // the code execution NEVER comes to here, WHY??
      }
    );
  }

【问题讨论】:

    标签: angular rxjs angular2-observables rxjs-observables


    【解决方案1】:

    我假设route 是注入的ActivatedRoute

    每个ActivatedRoute 绑定到一个路由组件,当路由发生变化时,当前显示的组件将被销毁,以及它的绑定ActivatedRoute,这就是你不会收到complete 通知的原因。


    Here'sActivatedRoute 是如何创建的:

    function createActivatedRoute(c: ActivatedRouteSnapshot) {
      return new ActivatedRoute(
          new BehaviorSubject(c.url), new BehaviorSubject(c.params), new BehaviorSubject(c.queryParams),
          new BehaviorSubject(c.fragment), new BehaviorSubject(c.data), c.outlet, c.component, c);
    }
    

    现在,ActivatedRoutes 是如何绑定到路由组件的?

    假设您的配置如下所示:

    {
      path: 'a/:id',
      component: AComponent,
      children: [
        {
          path: 'b',
          component: BComponent,
        },
        {
          path: 'c',
          component: CComponent,
        },
      ]
    }
    

    以及发布的 URL,例如 a/123/b

    你最终会拥有ActivatedRoutes 的

          APP
           |
           A
           |
           B
    

    每当您安排导航(例如router.navigateToUrl())时,它都必须经过一些重要阶段:

    • 应用重定向:检查重定向;加载延迟加载的模块;查找NoMatch 错误
    • 识别:创建ActivatedRouteSnapshot
    • 预激活:将结果树与当前树进行比较;此阶段还收集 canActivatecanDeactivate 警卫,基于发现的差异
    • 跑卫
    • 创建路由器状态:创建ActivatedRoute 树的位置
    • 激活路线:这是蛋糕上的樱桃,也是利用ActivatedRoute树的地方

      提到router-outlet 所扮演的角色也很重要。

      Angular 在 Map 对象的帮助下跟踪 router-outlets。

      Here's 在您的应用中有 <router-outlet></router-outlet> 时会发生什么:

    @Directive({selector: 'router-outlet', exportAs: 'outlet'})
    export class RouterOutlet implements OnDestroy, OnInit {
      private activated: ComponentRef<any>|null = null;
      private _activatedRoute: ActivatedRoute|null = null;
      private name: string;
    
      @Output('activate') activateEvents = new EventEmitter<any>();
      @Output('deactivate') deactivateEvents = new EventEmitter<any>();
    
      constructor(
          private parentContexts: ChildrenOutletContexts, private location: ViewContainerRef,
          private resolver: ComponentFactoryResolver, @Attribute('name') name: string,
          private changeDetector: ChangeDetectorRef) {
        this.name = name || PRIMARY_OUTLET;
        parentContexts.onChildOutletCreated(this.name, this);
      }
    }
    

    注意activated(它是一个组件)和_activatedRoute的存在!

    hereChildrenOutletContexts的相关位:

    export class ChildrenOutletContexts {
      // contexts for child outlets, by name.
      private contexts = new Map<string, OutletContext>();
    
      /** Called when a `RouterOutlet` directive is instantiated */
      onChildOutletCreated(childName: string, outlet: RouterOutlet): void {
        const context = this.getOrCreateContext(childName);
        context.outlet = outlet;
        this.contexts.set(childName, context);
      }
    }
    

    其中childName 默认为'primary'。现在,只关注context.outlet 部分。

    所以,对于我们的路由配置:

    {
      path: 'a/:id',
      component: AComponent,
      children: [
        {
          path: 'b',
          component: BComponent,
        },
        {
          path: 'c',
          component: CComponent,
        },
      ]
    }
    

    router-outlet Map 看起来像这样(大致):

    {
      primary: { // Where `AComponent` resides [1]
        children: {
          // Here `AComponent`'s children reside [2]
          primary: { children: { } }
        }
      }
    }
    

    现在,让我们看看how a route is activated

    // This block of code will be run for [1] and [2] (in this order!)
    const context = parentContexts.getOrCreateContext(future.outlet);
    /* ... */
    
    const config = parentLoadedConfig(future.snapshot);
    const cmpFactoryResolver = config ? config.module.componentFactoryResolver : null;
    
    context.attachRef = null;
    context.route = future;
    context.resolver = cmpFactoryResolver;
    if (context.outlet) {
      context.outlet.activateWith(future, cmpFactoryResolver);
    }
    
    this.activateChildRoutes(futureNode, null, context.children);
    

    context.outlet.activateWith(future, cmpFactoryResolver); 是我们正在寻找的(其中outletRouterOutlet 指令实例):

      activateWith(activatedRoute: ActivatedRoute, resolver: ComponentFactoryResolver|null) {
        if (this.isActivated) {
          throw new Error('Cannot activate an already activated outlet');
        }
        this._activatedRoute = activatedRoute;
        const snapshot = activatedRoute._futureSnapshot;
        const component = <any>snapshot.routeConfig!.component;
        resolver = resolver || this.resolver;
        const factory = resolver.resolveComponentFactory(component);
        const childContexts = this.parentContexts.getOrCreateContext(this.name).children;
        const injector = new OutletInjector(activatedRoute, childContexts, this.location.injector);
        this.activated = this.location.createComponent(factory, this.location.length, injector);
        // Calling `markForCheck` to make sure we will run the change detection when the
        // `RouterOutlet` is inside a `ChangeDetectionStrategy.OnPush` component.
        this.changeDetector.markForCheck();
        this.activateEvents.emit(this.activated.instance);
    }
    

    请注意,this.activated 包含 路由组件(例如 AComponent),this._activatedRoute 包含此组件的 ActivatedRoute

    现在让我们看看what happens,当我们导航到另一条路线并且当前视图被破坏时:

    deactivateRouteAndOutlet(
        route: TreeNode<ActivatedRoute>, parentContexts: ChildrenOutletContexts): void {
      const context = parentContexts.getContext(route.value.outlet);
    
      if (context) {
        const children: {[outletName: string]: any} = nodeChildrenAsMap(route);
        const contexts = route.value.component ? context.children : parentContexts;
    
        // Deactivate children first
        forEach(children, (v: any, k: string) => this.deactivateRouteAndItsChildren(v, contexts));
    
        if (context.outlet) {
          // Destroy the component
          context.outlet.deactivate();
          // Destroy the contexts for all the outlets that were in the component
          context.children.onOutletDeactivated();
        }
      }
    }
    

    RouterOutlet.deactivate() 看起来像like this

    deactivate(): void {
      if (this.activated) {
        const c = this.component;
        this.activated.destroy(); // Destroying the current component
        this.activated = null;
        // Nulling out the activated route - so no `complete` notification
        this._activatedRoute = null;
        this.deactivateEvents.emit(c);
      }
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-04-29
      相关资源
      最近更新 更多