在我的 Angular 2 路由模板之一(FirstComponent)中,我有一个按钮
first.component.html
<div class="button" click="routeWithData()">Pass data and route</div>
我的目标是实现:
按钮单击 -> 路由到另一个组件,同时保留数据并且不使用另一个组件作为指令。
这是我试过的...
第一种方法
在同一个视图中,我正在存储基于用户交互收集相同的数据。
first.component.ts
export class FirstComponent {
constructor(private _router: Router) { }
property1: number;
property2: string;
property3: TypeXY; // this a class, not a primitive type
// here some class methods set the properties above
// DOM events
routeWithData(){
// here route
}
}
通常我会通过
this._router.navigate(['SecondComponent']);
最终通过
this._router.navigate(['SecondComponent', {p1: this.property1, p2: property2 }]);
而带参数的链接的定义是
@RouteConfig([
// ...
{ path: '/SecondComponent/:p1:p2', name: 'SecondComponent', component: SecondComponent}
)]
这种方法的问题是我想我不能在 url 中传递复杂的数据(例如像 property3 这样的对象);
第二种方法
另一种方法是将 SecondComponent 作为指令包含在 FirstComponent 中。
<SecondComponent [p3]="property3"></SecondComponent>
但是我想路由到该组件,而不是包含它!
第三种方法
我在这里看到的最可行的解决方案是使用服务(例如 FirstComponentService)
在 FirstComponent 中的 routeWithData() 上存储数据 (_firstComponentService.storeData())
在 SecondComponent 的 ngOnInit() 中检索数据 (_firstComponentService.retrieveData())
虽然这种方法似乎完全可行,但我想知道这是否是实现目标的最简单/最优雅的方法。
一般来说,我想知道我是否错过了在组件之间传递数据的其他潜在方法,尤其是在代码量较少的情况下
Pass data using Query Parameters
是我一直在寻找的。您的 link 拯救了我的一天。
更新 4.0.0
有关详细信息,请参阅 Angular Angular Router - Fetch data before navigating。
原来的
使用服务是要走的路。在路由参数中,您应该只传递您希望反映在浏览器 URL 栏中的数据。
请参阅角度 Angular Cookbook Component Communication - Bidirectional Service。
RC.4 附带的路由器重新引入了 data
constructor(private route: ActivatedRoute) {}
const routes: RouterConfig = [
{path: '', redirectTo: '/heroes', pathMatch: 'full'},
{path: 'heroes', component: HeroDetailComponent, data: {some_data: 'some value'}}
];
class HeroDetailComponent {
ngOnInit() {
this.sub = this.route
.data
.subscribe(v => console.log(v));
}
ngOnDestroy() {
this.sub.unsubscribe();
}
}
另请参阅 Plunker。
我认为因为我们在 angular 2 中没有 $rootScope 类型的东西,就像在 angular 1.x 中那样。我们可以在 ngOnDestroy 中使用 Angular 2 共享服务/类将数据传递给服务,并在路由之后在 ngOnInit 函数中从服务中获取数据:
这里我使用 DataService 来分享 hero 对象:
import { Hero } from './hero';
export class DataService {
public hero: Hero;
}
从第一页组件传递对象:
ngOnDestroy() {
this.dataService.hero = this.hero;
}
从第二个页面组件中获取对象:
ngOnInit() {
this.hero = this.dataService.hero;
}
这是一个示例:plunker
this.hero = this.dataService.hero
吗?我会得到这些值吗?
Angular 7.2.0 引入了在路由组件之间导航时传递数据的新方法:
@Component({
template: `<a (click)="navigateWithState()">Go</a>`,
})
export class AppComponent {
constructor(public router: Router) {}
navigateWithState() {
this.router.navigateByUrl('/123', { state: { hello: 'world' } });
}
}
或者:
@Component({
selector: 'my-app',
template: `
<a routerLink="/details" [state]="{ hello: 'world' }">Go</a>`,
})
export class AppComponent {}
要读取状态,您可以在导航完成后访问 window.history.state
属性:
export class PageComponent implements OnInit {
state$: Observable<object>;
constructor(public activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.state$ = this.activatedRoute.paramMap
.pipe(map(() => window.history.state))
}
}
window.history.state
返回类似 {navigationId: 2}
的内容,而不是返回我传入的对象。
<div class="button" click="routeWithData()">Pass data and route</div>
好吧,我希望在 angular 6 或其他版本中做到这一点的最简单方法是简单地用你想要传递的数据量来定义你的路径
{path: 'detailView/:id', component: DetailedViewComponent}
从我的路由定义中可以看出,我添加了 /:id
来代表我想通过路由器导航传递给组件的数据。因此你的代码看起来像
<a class="btn btn-white-view" [routerLink]="[ '/detailView',list.id]">view</a>
为了读取组件上的 id
,只需像这样导入 ActivatedRoute
import { ActivatedRoute } from '@angular/router'
ngOnInit
上是您检索数据的位置
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
this.id = params['id'];
});
console.log(this.id);
}
您可以在这篇文章中阅读更多内容https://www.tektutorialshub.com/angular-passing-parameters-to-route/
我从这个页面查看了每个解决方案(并尝试了一些),但我不相信我们必须实现一种黑客方式来实现路由之间的数据传输。
简单 history.state
的另一个问题是,如果您在 state
对象中传递特定类的实例,则在接收它时它不会是该实例。但它将是一个简单的 JavaScript 对象。
所以在我的 Angular v10 (Ionic v5) 应用程序中,我这样做了——
this.router.navigateByUrl('/authenticate/username', {
state: {user: new User(), foo: 'bar'}
});
https://i.stack.imgur.com/vIVig.png
在导航组件 ('/authenticate/username'
) 中,在 ngOnInit()
方法中,我使用 this.router.getCurrentNavigation().extras.state
- 打印数据 -
ngOnInit() {
console.log('>>authenticate-username:41:',
this.router.getCurrentNavigation().extras.state);
}
https://i.stack.imgur.com/bSx7S.png
我得到了通过的所需数据-
https://i.stack.imgur.com/JGWc6.png
extras
?那是你刚刚定义的东西还是角度属性?
state
(在路由到新页面之后)对我来说仅在 constructor
上有效,在 ngOnInit
内无效。那是因为 getCurrentNavigation()
为空。
现在是 2019 年,这里的许多答案都会起作用,这取决于你想做什么。如果你想传递一些在 URL 中不可见的内部状态(参数、查询),你可以使用自 7.2 以来的 state
(就像我今天有 learned :))。
从博客(学分 Tomasz Kula) - 您导航到路线....
...来自 ts:this.router.navigateByUrl('/details', { state: { hello: 'world' } });
...来自 HTML 模板:<a routerLink="/details" [state]="{ hello: 'world' }">Go</a>
并在目标组件中获取它:
constructor(public activatedRoute: ActivatedRoute) {}
ngOnInit() {
this.state$ = this.activatedRoute.paramMap
.pipe(map(() => window.history.state))
}
迟到了,但希望这对最近使用 Angular 的人有所帮助。
state
不会丢失吗?能够原生地坚持它会很有趣。
我这是另一种方法不适合这个问题。我认为最好的方法是 Query-Parameter 通过 Router
角度有两种方式:
直接传递查询参数
使用此代码,您可以通过 html 代码中的 params
导航到 url
:
<a [routerLink]="['customer-service']" [queryParams]="{ serviceId: 99 }"></a>
通过路由器传递查询参数
您必须在 constructor
中注入路由器,例如:
constructor(private router:Router){
}
现在像这样使用:
goToPage(pageNum) {
this.router.navigate(['/product-list'], { queryParams: { serviceId: serviceId} });
}
现在,如果您想在另一个 Component
中读取 Router
,您必须使用 ActivatedRoute
,例如:
constructor(private activateRouter:ActivatedRouter){
}
subscribe
:
ngOnInit() {
this.sub = this.route
.queryParams
.subscribe(params => {
// Defaults to 0 if no query param provided.
this.page = +params['serviceId'] || 0;
});
}
一些不是我的超级聪明人(tmburnell)建议重写路线数据:
let route = this.router.config.find(r => r.path === '/path');
route.data = { entity: 'entity' };
this.router.navigateByUrl('/path');
如评论中的 here 所示。
我希望有人会觉得这很有用
使用 ActiveRoute 的解决方案(如果您想通过路由传递对象 - 使用 JSON.stringfy/JSON.parse):
发送前准备对象:
export class AdminUserListComponent {
users : User[];
constructor( private router : Router) { }
modifyUser(i) {
let navigationExtras: NavigationExtras = {
queryParams: {
"user": JSON.stringify(this.users[i])
}
};
this.router.navigate(["admin/user/edit"], navigationExtras);
}
}
在目标组件中接收您的对象:
export class AdminUserEditComponent {
userWithRole: UserWithRole;
constructor( private route: ActivatedRoute) {}
ngOnInit(): void {
super.ngOnInit();
this.route.queryParams.subscribe(params => {
this.userWithRole.user = JSON.parse(params["user"]);
});
}
}
super.ngOnInit();
是做什么用的?
路线:
{ path: 'foo-route', component: FooComponent, data: { myData: false } },
在组件中访问数据对象一次:
pipe(take(1))
立即取消订阅,因此没有内存泄漏,无需手动取消订阅
constructor(private activatedRoute: ActivatedRoute) { ... }
ngOnInit(): void {
this.activatedRoute.data.pipe(take(1)).subscribe((data) => {
console.log(data); // do something with the data
});
}
记得导入需要的东西
编辑:新的 firstValueFrom()
可能会更好
第三种方法是在组件之间共享数据的最常见方式。您可以在相关组件中注入您想要使用的项目服务。
import { Injectable } from '@angular/core';
import { Predicate } from '../interfaces'
import * as _ from 'lodash';
@Injectable()
export class ItemsService {
constructor() { }
removeItemFromArray<T>(array: Array<T>, item: any) {
_.remove(array, function (current) {
//console.log(current);
return JSON.stringify(current) === JSON.stringify(item);
});
}
removeItems<T>(array: Array<T>, predicate: Predicate<T>) {
_.remove(array, predicate);
}
setItem<T>(array: Array<T>, predicate: Predicate<T>, item: T) {
var _oldItem = _.find(array, predicate);
if(_oldItem){
var index = _.indexOf(array, _oldItem);
array.splice(index, 1, item);
} else {
array.push(item);
}
}
addItemToStart<T>(array: Array<T>, item: any) {
array.splice(0, 0, item);
}
getPropertyValues<T, R>(array: Array<T>, property : string) : R
{
var result = _.map(array, property);
return <R><any>result;
}
getSerialized<T>(arg: any): T {
return <T>JSON.parse(JSON.stringify(arg));
}
}
export interface Predicate<T> {
(item: T): boolean
}
使用 JSON 传递
<a routerLink = "/link"
[queryParams] = "{parameterName: objectToPass| json }">
sample Link
</a>
使用共享服务来存储具有自定义索引的数据。然后使用 queryParam 发送该自定义索引。这种方法更灵活。
// component-a : typeScript :
constructor( private DataCollector: DataCollectorService ) {}
ngOnInit() {
this.DataCollector['someDataIndex'] = data;
}
// component-a : html :
<a routerLink="/target-page"
[queryParams]="{index: 'someDataIndex'}"></a>
.
// component-b : typeScript :
public data;
constructor( private DataCollector: DataCollectorService ) {}
ngOnInit() {
this.route.queryParams.subscribe(
(queryParams: Params) => {
this.data = this.DataCollector[queryParams['index']];
}
);
}
说你有
组件1.ts 组件1.html
并且您想将数据传递给component2.ts。
在 component1.ts 中是一个带有数据的变量 //component1.ts item={name:"Nelson", bankAccount:"1百万美元"} //component1.html //line routerLink="/meter-readings/{ {item.meterReadingId}}" 与 // 无关,将其替换为您要导航到 查看 //component2.ts import { ActivatedRoute} from "@angular/router";导入'rxjs/add/operator/filter'; /*类名等和类样板 */ data:any //将保存我们传递给构造函数的最终对象(私有路由:ActivatedRoute,){} ngOnInit() { this.route.queryParams .filter(params => params .reading) .subscribe(params => { console.log(params); // 数据将是一个 JSON 字符串 - 我们解析返回我们的 //OBJECT this.data = JSON.parse(params.item) ; console. log(this.data,'PASSED DATA'); //给出 {name:"Nelson", bankAccount:"1 //百万美元"} }); }
您可以使用 BehaviorSubject 在路由组件之间共享数据。一个 BehaviorSubject 拥有一个值。当它被订阅时,它会立即发出值。主题不包含值。
在服务中。
@Injectable({
providedIn: 'root'
})
export class CustomerReportService extends BaseService {
reportFilter = new BehaviorSubject<ReportFilterVM>(null);
constructor(private httpClient: HttpClient) { super(); }
getCustomerBalanceDetails(reportFilter: ReportFilterVM): Observable<Array<CustomerBalanceDetailVM>> {
return this.httpClient.post<Array<CustomerBalanceDetailVM>>(this.apiBaseURL + 'CustomerReport/CustomerBalanceDetail', reportFilter);
}
}
在组件中,您可以订阅此 BehaviorSubject。
this.reportService.reportFilter.subscribe(f => {
if (f) {
this.reportFilter = f;
}
});
注意:主题在这里不起作用,只需要使用行为主题。
默认情况下,我不会为此使用警卫,更重要的是我可以进入路线还是离开路线。这不是在他们之间共享数据。
如果您想在我们输入路由之前加载数据,只需向此添加一个解析器,这也是路由器的一部分。
作为非常基本的示例:
解析器
import { Resolve, ActivatedRoute } from "@angular/router";
import { Observable } from "rxjs";
import { Injectable } from "@angular/core";
import { take } from "rxjs/operators";
@Injectable()
export class UserResolver implements Resolve<User> {
constructor(
private userService: UserService,
private route: ActivatedRoute
) {}
resolve(): Observable<firebase.User> {
return this.route.params.pipe(
switchMap((params) => this.userService.fetchUser(params.user_id)),
take(1)
);
}
}
放到路由器上:
RouterModule.forChild([
{
path: "user/:user_id",
component: MyUserDetailPage,
resolve: {
user: UserResolver
}
}
}]
获取我们组件中的数据
ngOnInit() {
const user: firebase.User = this.activatedRoute.snapshot.data.user;
}
这种方法的缺点是,如果他之前没有获得用户数据,他将首先进入路线,这确保用户的数据已经加载并在组件启动时准备好,但您将留在旧页面上数据加载时间长(加载动画)
一个很好的解决方案是使用 canActivate 方法实现 Guard。在这种情况下,您可以从给定的 api 获取数据并让用户访问路由文件中描述的组件。同时可以设置路由对象的数据属性并在组件中检索它。
假设你有这个路由配置:
const routes: Routes = [
{ path: "/:projectName", component: ProjectComponent, canActivate: [ProjectGuard] }
]`
在您的保护文件中,您可能有:
canActivate(next: ActivatedRouteSnapshot,state: RouterStateSnapshot)
: Observable<boolean> | Promise<boolean> | boolean {
return this.myProjectService.getProject(projectNameFoundElsewhere).pipe(
map((project) => {
if (project) {
next.data = project;
}
return !!project;
}),
);
}`
然后在你的组件中
constructor(private route: ActivatedRoute) {
this.route.data.subscribe((value) => (this.project = value));
}
这种方式与通过服务传递有点不同,因为只要没有取消设置,服务就会将值保留在 behaviorSubject 中。通过 tha 守卫使数据可用于当前路线。我没有检查子路由是否保留数据。
在需要将数据传递到另一个 Route 的场景中,最好和最简单的解决方案是使用 { window.localStorage }。此外,不要记住在使用结束后从本地存储中删除数据。我使用 ngOnDestroy 的 destroy() 方法来清理这些数据。这也解决了页面刷新导致数据丢失的问题。
如果您有一个公式集合来处理几个 ng 组件,这些组件基于类对象的集合/数组构建,大约保存。 10 个道具,例如包括输入值、标称值以及至少单位和布尔值……,因此要保持页面状态(输入+结果)会重复很多东西。
因此,我通过使用 *ngif 来模拟路由来显示单个页面的相关部分(组件),但从不更改 url。
<div *ngIf="visibleComponentA>
... All part of ComponetA
></div>
CpmponetA.html
<div *ngIf="visibleComponentB>
... All part of ComponetB
></div>
CpmponetB.html
这个布尔值将在组件的相关代码中设置:
@Input()visibleComponentA: boolean = true;
组件A.ts
现在在首页
<div (click)="OnClickNav(visibleComponentA)" >ComponentA</div>
<div (click)="OnClickNav(visibleComponentB)" >ComponentB</div>
app.component.html
和方法 OnClickNav(Selected:NavFlags) 切换组件的正确可见状态。
OnClickNav(Selected:NavFlags){
Selected.NavStatus=!Selected.NavStatus
Selected.NavItem=='visibleComponetA'? this.visibleComponetA.NavStatus=Selected.NavStatus: this.visibleComponetA.NavStatus= false;
Selected.NavItem=='visibleComponetB'? this.visibleComponetB.NavStatus=Selected.NavStatus: this.visibleComponetB.NavStatus= false;
应用程序.commonet.ts
NavFlags 类很简单
export class NavFlags {
NavItem: string = '';
NavStatus: boolean = false;
constructor(NavItem: string, NavStatus: boolean) {
this.NavItem = NavItem;
this.NavStatus = NavStatus;
}
}
导航标志.ts
这样,“个人”页面不会留下任何数据丢失。我没有重复的商店。完整的示例可以访问 https://angulartool.de。通过单击该按钮,可以在组件中浏览页面而不会丢失数据。
这个hack并不完美,所以也许会有更好的方法来解决这个角度问题。
不定期副业成功案例分享
ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
NavigationExtras
- stackoverflow.com/a/54879389/1148107 在路由器中传递状态