本文介绍了Angular observables 永远不会完成处理程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

Below is my code snippets. I wonder why it NEVER run into the completion handler?What I wanted to do is, first call serviceA to get objectA with given route param['key'], and then call serviceB to get objectB. So, objectB depends on result of objectA which depends on given param['key'].

附言我在 rxjs6 中使用 Angular 7

p.s. I am using Angular 7 with 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??
      }
    );
  }

推荐答案

我假设 route 是注入的 ActivatedRoute.

I assume that route is the injected ActivatedRoute.

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

Each ActivatedRoute is bound to a routed component and when a route changes occurs, the current component being displayed is going to be destroyed, as well as its bound ActivatedRoute, so that's why you won't get a complete notification.

这里是ActivatedRoute 是如何创建的:

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);
}

现在,ActivatedRoute如何绑定到路由组件?

Now, how are ActivatedRoutes bound to routed component?

假设您的配置如下所示:

Assuming you have a configuration that looks like this:

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

以及像 a/123/b

你最终会得到一棵ActivatedRoutes的:

you'd end up having a tree of ActivatedRoutes:

      APP
       |
       A
       |
       B

无论何时安排导航(例如 router.navigateToUrl()),它都必须经历一些重要阶段:

Whenever you schedule a navigation(e.g router.navigateToUrl()), it has to go through some important phases:

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

  • apply redirects: checking for redirects; loading lazy-loaded modules; finding NoMatch errors
  • recognize: creating the ActivatedRouteSnapshot tree
  • preactivation: comparing the resulted tree with the current one; this phase also collects canActivate and canDeactivate guards, based on the found differences
  • running guards
  • create router state: where ActivatedRoute tree is created
  • activating the routes: this is the cherry on the cake and the place where the ActivatedRoute tree is leveraged

同样重要的是要提到 router-outlet 所扮演的角色.

It is also important to mention the role that router-outlet plays.

Angular 在 Map 对象的帮助下跟踪 router-outlet.

Angular keeps track of the router-outlets with the help of a Map object.

这里是 当你的应用中有 时会发生什么:

Here's what happens when you have a <router-outlet></router-outlet> in your app:

@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! 的存在.

Note the presence of activated(which is a component) and _activatedRoute!.

还有这里ChildrenOutletContexts 的相关位:

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 部分.

where childName is by default 'primary'. For now, focus your attention only on the context.outlet part.

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

So, for our route configuration:

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

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

the router-outlet Map would look like this(roughly):

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

现在,让我们看看 如何激活路由:

// 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.

Note that this.activated holds the routed component(e.g AComponent) and this._activatedRoute holds the ActivatedRoute for this component.

现在让我们看看当我们导航到另一条路线并且当前视图被破坏时会发生什么:

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() 看起来 像这样:

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);
  }
}

这篇关于Angular observables 永远不会完成处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!

09-18 23:02