首页 文章

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

提问于
浏览
2

我正在构建一个角度为6的app,应该有两个特定的路由器插座:

<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检索token参数并对其执行一些检查 .

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

}

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

它在NavbarComponent中完美运行,但我无法在其他组件中获取参数 . 就像参数在导航栏中处理后丢失一样 .

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

这是stackblitz的一个例子

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

并输入此测试网址:https://angular-skvpsp.stackblitz.io/2345abcf/inbox有关详细信息,请参阅控制台 .

任何的想法?谢谢 .

[EDIT]

试过很多解决方案,但仍未找到正确的答案 .

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

1 回答

  • 2

    初步马克

    所以,经过一些调试我发现了问题 .
    很抱歉,在解决问题后,我只是为您的代码添加了一些tipp .

    TL; DR

    我为你准备了一个优化的Stackblitz,检查一下,看看所有的变化!

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

    原始问题

    看看你的路线:

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

    这些路线和评论存在问题

    • Angular消耗路径参数 . 所以 :token param只给AppComponent .
      以下可能解决方案

    • 无关,但也有代码气味:路由器内的AppComponent
      路由器使用基于url的组件填充 <router-outlet> 标记 . 由于AppComponent已经是根组件,因此您将AppComponent加载到其自身的RouterOutlet中 .

    • 您的导航栏仅适用,因为您让它与所有网址匹配 .
      由于你将它作为子路径放在 :token 路径下面,它也会得到 :token 参数,因为你读取它的逻辑位于BasePage.ts`中,它也可以读取它 .
      So your nav bar only works by accident.

    最简单的解决方案

    您最简单的解决方案是进入 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类的文档:
    https://angular.io/guide/router#activated-route

    更多建议

    将Stackblitz的应用程序链接集成为默认路由

    而不是发布Stackblitz和在stackblitz内部调用的链接,而是将stackblitz中的链接添加为默认路由 . 只需将此路线添加到您的路线阵列,您的Stackblitz将自动加载您想要的路线:

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

    不推荐使用

    ActivatedRoute.params和ActivatedRoute.queryParam

    请改用ActivatedRoute.paramMap和ActivatedRoute.queryParamMap .
    见官方角度文档:https://angular.io/guide/router#activated-route

    尽可能使用paramMaps的Observable版本

    您应该每次使用paramMaps作为Observable而不是snapshot.paramMaps .

    快照的问题如下:

    • 路由器激活Component1

    • 例如,在ngOnInit中,ParamSnapshot为红色

    • 路由器激活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‘> 应该加载路线/状态/在线 .

    由于您省略了第二个插座的最后一个部分,因此不能立即清楚,角度将如何处理路线 .

    请勿使用第二个路由器插座进行导航

    这使事情变得比他们需要的更复杂 . 您无需单独浏览导航内容,也不需要?如果您不需要单独导航,则可以更轻松地将菜单直接包含在AppComponent模板中 .

    当你有一个侧边栏进行聊天时,第二个路由器插座是有意义的,你可以选择联系人,聊天窗口和聊天设置页面,而所有这些都独立于你的主要方面 . 那你需要两个路由器插座 .

    使用基类或角度服务

    您通过以下方式将您的BasePage作为服务提供给Angular

    @Injectable({
        providedIn: ‚root‘
    })
    

    使用可注入服务作为基类似乎是一个非常奇怪的设计 . 我想不出一个原因,我想用它 . 要么我使用BaseClass作为BaseClass,它独立于角度工作 . 或者我使用角度服务并将其注入到需要服务功能的组件中 .

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

    解决方案:请参阅'更好解决方案:TokenComponent with Token Service'如下 .

    更好的解决方案:使用令牌服务的TokenComponent

    我现在能想到的最好的解决方案是与TokenService一起构建TokenComponent . 您可以在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' }
    ];
    

    如何立即访问令牌

    • 在您需要令牌的地方注入TokenService .

    • 当您在模板中需要它时,使用异步管道访问它,如下所示:

    <p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
    
    • 当您在typescript内部使用令牌时,像任何其他可观察对象一样访问它(在收件箱组件中的示例)
    this.tokenService.token$
    .subscribe(token => console.log(`Token in Inbox Component: ${token}`));
    

    我希望这会对你有所帮助 . 这是一个有趣的挑战! :)

相关问题