多个路由器出口丢失 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' }
]
}
];
这些路线和评论有问题
- Angular 消耗路径参数。
所以
:token
参数只提供给 AppComponent。
可能的解决方案如下。
- 不相关,但也有代码味道:路由器内部的 AppComponent
路由器使用基于 url 的组件填充 <router-outlet>
标签。
由于 AppComponent 已经是根组件,所以你加载 AppComponent
进入它自己的 RouterOutlet。
- 您的导航栏只起作用,因为您让它匹配所有网址。
由于您将它作为子路由放在 :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 服务并将其注入到需要该服务功能的组件中。
注意:您不能简单地创建一个服务,它会获得 Router
和 ActivatedRoute
注入,因为它会获得根对象这些组件,它们是空的。
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' }
];
现在如何获取令牌
在需要Token的地方注入TokenService
当你在模板中需要它时,像这样使用异步管道访问它:
<p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
当您在打字稿中需要令牌时,像访问任何其他可观察对象一样访问它(收件箱组件中的示例)
this.tokenService.token$
.subscribe(token => console.log(`Token in Inbox Component: ${token}`));
希望对您有所帮助。这是一个有趣的挑战! :)
我正在构建一个 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' }
]
}
];
这些路线和评论有问题
- Angular 消耗路径参数。
所以
:token
参数只提供给 AppComponent。
可能的解决方案如下。 - 不相关,但也有代码味道:路由器内部的 AppComponent
路由器使用基于 url 的组件填充<router-outlet>
标签。 由于 AppComponent 已经是根组件,所以你加载 AppComponent 进入它自己的 RouterOutlet。 - 您的导航栏只起作用,因为您让它匹配所有网址。
由于您将它作为子路由放在: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 服务并将其注入到需要该服务功能的组件中。
注意:您不能简单地创建一个服务,它会获得 Router
和 ActivatedRoute
注入,因为它会获得根对象这些组件,它们是空的。
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' }
];
现在如何获取令牌
在需要Token的地方注入TokenService
当你在模板中需要它时,像这样使用异步管道访问它:
<p>Token: {{tokenService.token$ | async}} (should be 2345abcf)</p>
当您在打字稿中需要令牌时,像访问任何其他可观察对象一样访问它(收件箱组件中的示例)
this.tokenService.token$ .subscribe(token => console.log(`Token in Inbox Component: ${token}`));