问题描述
下面是我的代码片段.我想知道为什么它永远不会遇到完成处理程序?我想要做的是,首先调用 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 ActivatedRoute
s 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
你最终会得到一棵ActivatedRoute
s的树:
you'd end up having a tree of ActivatedRoute
s:
APP
|
A
|
B
无论何时安排导航(例如 router.navigateToUrl()
),它都必须经历一些重要阶段:
Whenever you schedule a navigation(e.g router.navigateToUrl()
), it has to go through some important phases:
- 应用重定向:检查重定向;加载延迟加载的模块;查找
NoMatch
错误 - 识别:创建
ActivatedRouteSnapshot
树 - 预激活:将结果树与当前树进行比较;这个阶段还收集
canActivate
和canDeactivate
守卫,基于发现的差异 - 跑卫
- 创建路由器状态:
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
andcanDeactivate
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-outlet
s 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);
是我们正在寻找的(其中 outlet
是 RouterOutlet
指令实例):
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 永远不会完成处理程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!