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

问题描述 投票:3回答:1

我正在构建一个角度为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有关详细信息,请参阅控制台。

任何的想法?谢谢。

[编辑]

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

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

angular routing angular6 router-outlet
1个回答
3
投票

Preliminary Mark

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

TL;DR

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

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

Original Problem

看看你的路线:

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

这些路线和评论存在问题

  1. Angular消耗路径参数。所以:token param只给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类的文档: https://angular.io/guide/router#activated-route

Some More Advice

将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,它独立于角度工作。或者我使用角度服务并将其注入到需要服务功能的组件中。

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

解决方案:请参阅下面的“更好的解决方案:TokenComponent with Token Service”。

Better solution: TokenComponent with Token Service

我现在能想到的最好的解决方案是与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. 在需要令牌的地方注入TokenService。
  2. 当您在模板中需要它时,使用异步管道访问它,如下所示: <p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
  3. 当您在typescript内部使用令牌时,像任何其他可观察对象一样访问它(在收件箱组件中的示例) this.tokenService.token$ .subscribe(token => console.log(`Token in Inbox Component: ${token}`));

I hope this helps you. This has been a fun challenge! :)

© www.soinside.com 2019 - 2024. All rights reserved.