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

时间:2018-08-31 06:44:45

标签: angular routing angular6 router-outlet

我正在构建一个角度为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中检索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 有关更多信息,请参见控制台。

有什么主意吗?谢谢。

[编辑]

尝试了许多解决方案,但仍未找到正确的答案。

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

1 个答案:

答案 0 :(得分:3)

初步商标

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

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' }
    ]
}
];

这些路线和注释问题

  1. Angular使用路径参数。 因此:token参数仅提供给AppComponent。
    可能的解决方法如下。
  2. 不相关,但也有代码气味:路由器内部的AppComponent
    路由器使用基于URL的组件填充<router-outlet>标签。 由于AppComponent已经是根组件,因此您可以加载AppComponent 进入自身的R​​outerOutlet。
  3. 您的导航栏仅适用,因为您让它匹配所有URL。
    由于您将其作为子路由放置在: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

更多建议

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

与其发布您的Stackblitz以及在stackblitz内部进行调用的链接, 将您的链接添加到stackblitz中作为默认路由。 只需将此路由添加到您的路由数组中,即可加载Stackblitz 所需的自动路线:

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

不推荐使用ActivatedRoute.params和ActivatedRoute.queryParam

请改用ActivatedRoute.paramMap和ActivatedRoute.queryParamMap。
查看官方的Angular文档:https://angular.io/guide/router#activated-route

尽可能使用paramMap的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‘>应该加载路由/ status / online。

由于您省略了第二个出口的最后一部分,因此尚不清楚角度如何处理路线。

请勿将第二个路由器插座仅用于导航

这使事情变得比实际复杂。 您不需要与内容分开导航,还是? 如果您不需要单独的导航,则可以轻松地将菜单直接包含到AppComponent模板中。

当您有诸如边栏聊天之类的东西时,第二个路由器插座才有意义,您可以在其中选择联系人,拥有聊天窗口和聊天设置页面,而所有这些都独立于您的主要方面。 然后,您将需要两个路由器出口。

使用基类或角度服务

您通过

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

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

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

解决方案:请参阅下面的“更好的解决方案:具有令牌服务的TokenComponent”。

更好的解决方案:具有令牌服务的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' }
];

如何立即访问令牌

  1. 在需要令牌的地方注入TokenService。
  2. 当您在模板中需要它时,请使用如下所示的异步管道进行访问:

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

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

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