多个路由器出口丢失 url 参数

Multiple router-outlet lose url parameters

我正在构建一个 angular 6 应用程序,它应该有两个特定的 router-outlet:

<router-outlet name="navbar"></router-outlet>
<router-outlet></router-outlet>

该应用程序由 URL 中指定的令牌驱动:

http://my.app/*token*/dashboard

所以我有这个特定的路由:

const routes: Routes = [
    {
        path: ':token', component: CoreComponent, children: [
            { path: 'dashboard', component: DashboardComponent },
            { path: 'app1', component: App1Component },
            { path: 'app2', component: App2Component },
            { path: '', component: NavbarComponent, outlet: 'navbar' }
        ]
    }
];

@NgModule({
    imports: [
        CommonModule,
        RouterModule.forRoot(routes)
    ],
    exports: [RouterModule],
    declarations: []
})

在每个组件中,我从 URL 中检索令牌参数并对其执行一些检查。

constructor(public route: ActivatedRoute,
    public router: Router) {

}

ngOnInit() {
    this.route.params.subscribe(params => {
        let _token: string = params['token'];
        // do something
    });
}

它在 NavbarComponent 中完美运行,但我无法在其他组件中获取参数。好像参数在navbar处理完就丢了

我还没有找到关于这种现象的任何可靠文件。

这是 stackblitz 上的一个例子

https://stackblitz.com/edit/angular-skvpsp?file=src%2Fapp%2Fdashboard.component.ts

然后进入这个测试url:https://angular-skvpsp.stackblitz.io/2345abcf/inbox 有关详细信息,请参阅控制台。

有什么想法吗?谢谢。

[编辑]

尝试了很多解决方案,但仍然没有找到正确的答案。

我看到有一个数据属性可以在路由模块中使用。我会检查这个。

初步标记

所以,经过一番调试,我发现了问题。
很抱歉回答很长,我只是在解决您的问题后为您的代码添加了一些提示。

TL;DR

我为您准备了优化的 Stackblitz,请查看所有更改!

https://stackblitz.com/edit/Whosebug-question-52109889

原题

看看你的路线:

const routes: Routes = [
{
    path: ':token', component: AppComponent, children: [
        { path: 'dashboard', component: DashboardComponent },
        { path: 'inbox', component: InboxComponent },
        { path: '', component: NavbarComponent, outlet: 'navbar' }
    ]
}
];

这些路线和评论有问题

  1. Angular 消耗路径参数。 所以 :token 参数只提供给 AppComponent。
    可能的解决方案如下。
  2. 不相关,但也有代码味道:路由器内部的 AppComponent
    路由器使用基于 url 的组件填充 <router-outlet> 标签。 由于 AppComponent 已经是根组件,所以你加载 AppComponent 进入它自己的 RouterOutlet。
  3. 您的导航栏只起作用,因为您让它匹配所有网址。
    由于您将它作为子路由放在 :token 路径下方,因此它也会得到 :token 参数,因为您读取它的逻辑位于内部 BasePage.ts` 也能读出来
    所以你的导航栏只是偶然的。

最简单的解决方案

您最简单的解决方案是进入 BasePage.ts 并更改以下代码

this.queryParams = this.route.snapshot.queryParams
this.routeParams = this.route.snapshot.params;  

this.queryParams = this.route.parent.snapshot.queryParams
this.routeParams = this.route.parent.snapshot.params;  

这是 ActivatedRoute 的文档 class:
https://angular.io/guide/router#activated-route

更多建议

为 Stackblitz 集成您的应用 link 作为默认路由

而不是发布你的 Stackblitz 和 link 在 stackblitz 中调用, 在 stackblitz 中添加你的 link 作为默认路由。 只需将这条路线添加到您的路线数组中,您的 Stackblitz 就会加载 自动选择您想要的路线:

{ path: '', pathMatch: 'full', redirectTo: '2345abcf/inbox' }

ActivatedRoute.params 和 ActivatedRoute.queryParam 已弃用

请改用ActivatedRoute.paramMap和ActivatedRoute.queryParam地图。
请参阅官方 angular 文档:https://angular.io/guide/router#activated-route

尽可能使用paramMaps的Observable版本

你几乎每次都应该使用 paramMaps 作为 Observable 而不是 snapshot.paramMaps。

快照问题如下:

  • 路由器激活 Component1
  • ParamSnapshot 例如在 ngOnInit 中是红色的
  • 路由器激活 Component2
  • 路由器再次激活 Component1,但使用另一个路径参数。
    2个问题:
    • ParamSnapshot 是不可变的,因此无法访问新值
    • ngOnInit 不会再次 运行,因为组件已经在 dom.

使用 routerLink 属性构建你的路由

参见:https://angular.io/guide/router#router-links 这避免了手动构建路由时可能出现的错误。 通常,具有两个路由器出口的路由如下所示:

https://some-url.com/home(conference:status/online)

/home 是主要路线,(conference:status/online) 表示 <router-outlet name=‚conference‘> 应该加载路由 /status/online.

由于您省略了第二个出口的最后一部分,所以目前还不清楚 angular 将如何处理路线。

不要使用第二个路由器插座仅用于导航

这让事情变得比他们需要的更复杂。 您不需要将导航与内容分开导航,对吗? 如果您不需要单独的导航,直接将菜单包含到 AppComponent 模板中会更容易。

如果你有聊天侧边栏之类的东西,你可以 select 联系,聊天 window 和聊天设置页面,那么第二个路由器插座就有意义了独立于你的主要方面。 那么你需要两个路由器插座。

使用基础 class 或 angular 服务

您通过

将您的 BasePage 作为服务提供给 Angular
@Injectable({
    providedIn: ‚root‘
})

使用可注入服务作为基础 class 似乎是一个非常奇怪的设计。 我想不出一个理由,我想用它。 要么我使用 BaseClass 作为 BaseClass,它独立于 angular 工作。 或者我使用 angular 服务并将其注入到需要该服务功能的组件中。

注意:您不能简单地创建一个服务,它会获得 RouterActivatedRoute 注入,因为它会获得根对象这些组件,它们是空的。 Angular 为每个组件生成它自己的 ActivatedRoute 实例并将其注入到组件中。

解决方法:见下文'Better solution: TokenComponent with Token Service'。

更好的解决方案:带有令牌服务的 TokenComponent

目前我能想到的最好的解决方案是将 TokenComponent 与 TokenService 一起构建。 您可以在 Stackblitz 查看此想法的工作副本:

https://stackblitz.com/edit/angular-l1hhj1

现在是代码的重要部分,以防 Stackblitz 被删除。

我的 TokenService 看起来像这样:

import { OnInit, Injectable } from '@angular/core';
import { Observable, ReplaySubject } from 'rxjs';

@Injectable({
  providedIn: 'root'
})
export class TokenService {

 /**
  * Making the token an observable avoids accessing an 'undefined' token.
  * Access the token value in templates with the angular 'async' pipe. 
  * Access the token value in typescript code via rxjs pipes and subscribe.
  */
  public token$: Observable<string>;

  /**
   * This replay subject emits the last event, if any, to new subscribers.
   */
  private tokenSubject: ReplaySubject<string>;

  constructor() {
    this.tokenSubject = new ReplaySubject(1);
    this.token$ = this.tokenSubject.asObservable();
  }

  public setToken(token: string) {
    this.tokenSubject.next(token);
  }

}

而这个 TokenService 在 ngOnInit 中的 TokenComponent 中使用:

import { Component, OnInit } from '@angular/core';
import { ActivatedRoute, Router, ParamMap } from '@angular/router';
import { Observable } from 'rxjs';
import { map, tap } from 'rxjs/operators';
import { TokenService } from './token.service';

@Component({
  selector: 'app-token',
  template: `
  <p>Token in TokenComponent (for Demonstration): 
     {{tokenService.token$ | async}}
  </p>
  <!--
      This router-outlet is neccessary to hold the child components
      InboxComponent and DashboardComponent
  -->
  <router-outlet></router-outlet>
    `
})
export class TokenComponent implements OnInit {


  public mytoken$: Observable<string>;

  constructor(
    public route: ActivatedRoute,
    public router: Router,
    public tokenService: TokenService
  ) {

  }

  ngOnInit() {
    this.route.paramMap.pipe(
      map((params: ParamMap) => params.get('token')),
      // this 'tap' - pipe is only for demonstration purposes
      tap((token) => console.log(token))
    )
      .subscribe(token => this.tokenService.setToken(token));
  }

}

最后,路由配置将它们粘合在一起:

const routes: Routes = [
  {
    path: ':token', component: TokenComponent, children: [
      { path: '', pathMatch: 'full', redirectTo: 'inbox'},
      { path: 'dashboard', component: DashboardComponent },
      { path: 'inbox', component: InboxComponent },
    ]
  },
  { path: '', pathMatch: 'full', redirectTo: '2345abcf/inbox' }
];

现在如何获取令牌

  1. 在需要Token的地方注入TokenService

  2. 当你在模板中需要它时,像这样使用异步管道访问它:

     <p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
    
  3. 当您在打字稿中需要令牌时,像访问任何其他可观察对象一样访问它(收件箱组件中的示例)

     this.tokenService.token$
     .subscribe(token => console.log(`Token in Inbox Component: ${token}`));
    

希望对您有所帮助。这是一个有趣的挑战! :)