【问题标题】:How do I pass data to Angular routed components?如何将数据传递给 Angular 路由组件?
【发布时间】:2016-08-18 12:32:15
【问题描述】:

在我的 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
    }
}

通常我会通过

路由到 SecondComponent
 this._router.navigate(['SecondComponent']);

最终通过数据传递

 this._router.navigate(['SecondComponent', {p1: this.property1, p2: property2 }]);

而带有参数的链接的定义是

@RouteConfig([
      // ...
      { path: '/SecondComponent/:p1:p2', name: 'SecondComponent', component: SecondComponent} 
)]

这种方法的问题是我猜 我无法在 URL 中传递复杂的数据(例如,像 property3 这样的 object);

第二种方法

另一种方法是将 SecondComponent 作为 directive 包含在 FirstComponent 中。

  <SecondComponent [p3]="property3"></SecondComponent>

但是我想路由到那个组件,而不是包含它!

第三种方法

我在这里看到的最可行的解决方案是使用 Service(例如 FirstComponentService)来

  • 将数据 (_firstComponentService.storeData()) 存储在 FirstComponent 中的 routeWithData() 上
  • SecondComponentngOnInit()检索数据 (_firstComponentService.retrieveData())

虽然这种方法似乎完全可行,但我想知道这是否是实现目标的最简单/最优雅的方法。

总的来说,我想知道我是否遗漏了其他可能的方法在组件之间传递数据,尤其是代码量较少

【问题讨论】:

  • 感谢@Prashobh。 Pass data using Query Parameters 是我想要的。你的 link 拯救了我的一天。
  • Angular 7.2 现在具有使用state 在路由之间传递数据的新功能,请查看PR 了解更多详细信息。一些有用的信息here
  • @Prashobh 非常感谢。您分享的链接非常有用

标签: angular typescript


【解决方案1】:

更新 4.0.0

有关详细信息,请参阅 Angular Angular Router - Fetch data before navigating

原创

使用服务是要走的路。在路由参数中,您应该只传递您希望反映在浏览器 URL 栏中的数据。

请参阅 Angular 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.1.0 仍然有效吗?
  • RC.4 路由器数据仅用于静态数据。您不能将不同的数据发送到同一条路线,它总是必须是相同的数据我错了吗?
  • 不,为此用例使用共享服务。
  • 在 Angular 5 中,无论如何,你应该能够... ngOnInit() { this.myVar = this.route.snapshot.data['some_data']; }
  • 如果您能够使用 Angular v7.2,它现在允许使用 NavigationExtras - stackoverflow.com/a/54879389/1148107 在路由器中传递状态
【解决方案2】:

路线:

{ 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
  });
}
  • 记得导入需要的东西

【讨论】:

    【解决方案3】:

    在需要将数据传递到另一个 Route 的场景中,最好和最简单的解决方案是使用 { window.localStorage }。此外,一旦使用结束,不要记得从本地存储中删除数据。 我使用 ngOnDestroy 的 destroy() 方法来清理这些数据。 这也解决了页面刷新导致数据丢失的问题。

    【讨论】:

    • 点评来源: 你能提供一些示例代码吗?
    【解决方案4】:

    默认情况下,我不会为此使用警卫,更重要的是我可以进入路线还是离开路线。不是在他们之间共享数据。

    如果您想在我们输入路由之前加载数据,只需向此添加一个解析器,这也是路由器的一部分。

    作为一个非常基本的例子:

    解析器

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

    这种方法的缺点是,如果他之前没有获得用户数据,他将首先进入路由,这确保用户的数据已经加载并在组件启动时准备好,但你会留在旧页面只要数据已经加载(加载动画)

    【讨论】:

      【解决方案5】:

      一个很好的解决方案是使用 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 守卫使数据可用于当前路线。我没有检查子路由是否保留数据。

      【讨论】:

        【解决方案6】:

        我查看了此页面中的每个解决方案(并尝试了一些),但我不相信我们必须实施一种类似 hack 的方式来实现路由之间的数据传输。

        简单history.state 的另一个问题是,如果您在state 对象中传递特定类的实例,则在接收它时它不会是该实例。但它将是一个简单的 JavaScript 对象。

        所以在我的 Angular v10 (Ionic v5) 应用程序中,我这样做了-

        this.router.navigateByUrl('/authenticate/username', {
            state: {user: new User(), foo: 'bar'}
        });
        

        在导航组件('/authenticate/username')中,在ngOnInit()方法中,我用this.router.getCurrentNavigation().extras.state打印了数据-

        ngOnInit() {
            console.log('>>authenticate-username:41:',
                this.router.getCurrentNavigation().extras.state);
        }
        

        我得到了想要的数据,它通过了-

        【讨论】:

        • extras ?这是你刚刚定义的东西还是角度属性?
        • 正是我要找的东西,谢谢队友..这是给你的投票;)我也在 ionic5 proj 中使用它
        • 精彩的答案!重要的是要记住访问state(在路由到新页面之后)对我来说只在constructor上有效,而不是在ngOnInit内。那是因为getCurrentNavigation() 为空。
        • @Itay 我同意。我正在使用 Angular 11。当前导航范围在 ngOnInit() 之前结束。所以我不得不从构造函数中获取状态值。
        • 刷新页面会丢失数据
        【解决方案7】:

        您可以使用 BehaviorSubject 在路由组件之间共享数据。 一个 BehaviorSubject 拥有一个值。当它被订阅时,它会立即发出值。 Subject 没有值。

        在服务中。

        @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;
              }
            });
        

        注意:主题在这里不起作用,只需要使用行为主题。

        【讨论】:

          【解决方案8】:

          现在是 2019 年,这里的许多答案都会奏效,这取决于你想做什么。如果你想传递一些在 URL 中不可见的内部状态(参数、查询),你可以使用自 7.2 以来的 state(就像我今天有 learned 一样:)。

          来自博客(感谢 Tomasz Kula) - 您导航到路线......

          ...来自ts:this.router.navigateByUrl('/details', { state: { hello: 'world' } });

          ...来自 HTML 模板:&lt;a routerLink="/details" [state]="{ hello: 'world' }"&gt;Go&lt;/a&gt;

          并在目标组件中拾取它:

          constructor(public activatedRoute: ActivatedRoute) {}
          
            ngOnInit() {
              this.state$ = this.activatedRoute.paramMap
                .pipe(map(() => window.history.state))
            }
          

          晚了,但希望这对最近使用 Angular 的人有所帮助。

          【讨论】:

          • 用户刷新时state不是丢失了吗?能够以原生方式持久化它会很有趣。
          • 这实际上是唯一对我有用的方法。谢谢?
          【解决方案9】:

          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} 的内容,而不是返回我传入的对象。
          • @Louis 您使用的是哪个 Angular 版本?
          • 我使用的是 Angular 版本 8.1.0
          • 我看到的和 Louis 一样,版本比他低,但仍然足够高,应该有这个功能。
          • 状态对象的浏览器数据大小限制为 640k。 stackoverflow.com/questions/24425885/…
          【解决方案10】:

          说你有

          1. component1.ts
          2. component1.html

          并且您想将数据传递给 component2.ts

          • 在component1.ts中是一个带有数据的变量

              //component1.ts
              item={name:"Nelson", bankAccount:"1 million dollars"}
            
              //component1.html
               //the line routerLink="/meter-readings/{{item.meterReadingId}}" has nothing to 
              //do with this , replace that with the url you are navigating to
              <a
                mat-button
                [queryParams]="{ params: item | json}"
                routerLink="/meter-readings/{{item.meterReadingId}}"
                routerLinkActive="router-link-active">
                View
              </a>
            
              //component2.ts
              import { ActivatedRoute} from "@angular/router";
              import 'rxjs/add/operator/filter';
            
              /*class name etc and class boiler plate */
              data:any //will hold our final object that we passed 
              constructor(
              private route: ActivatedRoute,
              ) {}
            
             ngOnInit() {
            
             this.route.queryParams
              .filter(params => params.reading)
              .subscribe(params => {
              console.log(params); // DATA WILL BE A JSON STRING- WE PARSE TO GET BACK OUR 
                                   //OBJECT
            
              this.data = JSON.parse(params.item) ;
            
              console.log(this.data,'PASSED DATA'); //Gives {name:"Nelson", bankAccount:"1 
                                                    //million dollars"}
               });
              }
            

          【讨论】:

            【解决方案11】:

            不是我的一些超级聪明人(tmburnell)建议重写路线数据:

            let route = this.router.config.find(r => r.path === '/path');
            route.data = { entity: 'entity' };
            this.router.navigateByUrl('/path');
            

            正如在 cmets 中看到的 here

            我希望有人会觉得这很有用

            【讨论】:

            • 刚刚发现了这一点,我觉得我需要一些 stackoverflow 点 :)
            【解决方案12】:

            我这是另一种方法不适合这个问题。 我认为最好的方法是 Query-Parameter Router angular 有两种方式:

            直接传递查询参数

            使用此代码,您可以在 html 代码中通过 params 导航到 url

            <a [routerLink]="['customer-service']" [queryParams]="{ serviceId: 99 }"></a>
            

            通过Router传递查询参数

            您必须在您的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;
                  });
              }
            

            【讨论】:

            • this.router.navigate(['/product-list'], { queryParams: { serviceId: serviceId} });可以替换成 this.router.navigate(['/product-list'], { queryParams: { serviceId} });
            【解决方案13】:

            使用共享服务来存储具有自定义索引的数据。然后使用 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']];
                    }
                );
            }
            

            【讨论】:

              【解决方案14】:
              <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/

              【讨论】:

              • 如果我想发送一个复杂的对象怎么办?我不想让我的路线膨胀到无法维护的废话:(
              • @cmxl 那就使用共享服务吧。
              • @cmxl 仅发送 id 或简单字符串作为数据的想法是使 URL 更“可共享”并且易于被机器人等抓取。因此,生成的链接可以由以下用户共享你的应用程序。对于发送更大的对象,服务会更有效。
              【解决方案15】:

              使用 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"]);
                    });
                }
              
              }
              

              【讨论】:

              • 这行得通,但如果我不想公开 URL 中的所有数据怎么办?
              • 您可以将数据加密放入参数中,然后在目标组件中加密。
              • 我已经created the service 用于数据共享。
              • super.ngOnInit(); 是干什么用的?
              • 谢谢。发送端的 JSON.stringify() 和接收端的 JSON.parse() 对我有用。
              【解决方案16】:

              使用 JSON 传递

                <a routerLink = "/link"
                 [queryParams] = "{parameterName: objectToPass| json }">
                       sample Link                   
                </a>
              

              【讨论】:

              • 如果您可以显示参数在接收组件中的使用方式以及它所采用的整个路线,这将是一个更好的答案。这意味着如果有人不知道如何传递参数,他也不会知道如何在接收组件中使用此参数。 :)
              • 这样做的一个缺点是查询字符串有大小限制,有时您不希望在地址栏中显示对象属性。
              【解决方案17】:

              我认为因为我们在 angular 2 中没有像 angular 1.x 中那样的 $rootScope 类型的东西。我们可以在 ngOnDestroy 中使用 Angular 2 共享服务/类将数据传递给服务,并在路由之后在 ngOnInit 函数中从服务中获取数据:

              这里我使用DataService来分享英雄对象:

              import { Hero } from './hero';
              export class DataService {
                public hero: Hero;
              }
              

              从第一页组件传递对象:

               ngOnDestroy() {
                  this.dataService.hero = this.hero; 
               }
              

              从第二个页面组件中获取对象:

               ngOnInit() {
                  this.hero = this.dataService.hero; 
               }
              

              这里是一个例子:plunker

              【讨论】:

              • 这很漂亮,但是这在 Ng2 社区中有多普遍?我不记得在文档中阅读过它...
              • 与 url 参数或其他浏览器存储等其他选项相比,这在我看来更好。我也没有在任何文档中看到像这样工作。
              • 当用户打开一个新标签并复制粘贴第二个组件路由时它是否有效?我可以获取this.hero = this.dataService.hero 吗?我会得到这些值吗?
              • 这确实非常简单,每个 Angular 开发人员都知道,但问题是一旦您刷新服务中的松散数据。用户将不得不再次执行所有操作。
              • @SantoshKadam 问题是“如何将数据传递给 Angular 路由组件?”所以通过 ngOnDestroy 和 ngOnInit 函数传递数据是一种方法,而且总是简单是最好的。如果用户需要在重新加载后获取数据,则需要将数据保存在永久存储中并从该存储中再次读取。
              【解决方案18】:

              第三种方法是在组件之间共享数据的最常用方法。您可以在相关组件中注入您想要使用的项目服务。

              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
              }
              

              【讨论】:

              • 服务在切换路由时被实例化。所以你会丢失数据
              • @JimmyKane 您具体谈到页面刷新时,但如果它不刷新,则内存仍保存在服务中。这应该是默认行为,因为它会多次保存加载。
              • @AaronRabinowitz 对。对困惑感到抱歉。并对投反对票感到抱歉。希望我现在可以撤消它。太晚了。是 angular 2 的新手,我在尝试您的方法时遇到的问题是我为许多组件提供了服务,而不是通过 app 模块提供。
              猜你喜欢
              • 1970-01-01
              • 2016-09-06
              • 1970-01-01
              • 2016-09-21
              • 2019-11-05
              • 1970-01-01
              • 2017-02-28
              • 2020-02-06
              • 1970-01-01
              相关资源
              最近更新 更多