【问题标题】:Change route params without reloading in Angular 2更改路由参数而不在 Angular 2 中重新加载
【发布时间】:2016-06-07 16:56:09
【问题描述】:

我正在使用 Angular 2、谷歌地图等制作房地产网站,当用户更改地图中心时,我会搜索 API,指示地图的当前位置以及半径。问题是,我想在不重新加载整个页面的情况下在 url 中反映这些值。那可能吗?我找到了一些使用 AngularJS 1.x 的解决方案,但没有找到关于 Angular 2 的解决方案。

【问题讨论】:

  • 我认为如果你使用 [routerLink]="['/route', { param1: value1 }] 它不会重新加载页面
  • 但是如何添加另一个查询参数?
  • ☝️ 会导致页面重新加载
  • 请注意,如果您使用 SSR 使您的网站 SEO 兼容,这是一个没有实际意义的问题。
  • @Jonathan,是吗?由于 Angular 会在呈现静态页面后接管路由,所以我认为即使使用 SSR,它仍然是一个有效的问题。

标签: javascript angular routes angular2-routing


【解决方案1】:

你可以使用location.go(url),它基本上会改变你的网址,而不会改变申请路线。

注意这可能会导致其他影响,例如从当前路由重定向到子路由。

描述location.goRelated question不会与Router发生变化。

【讨论】:

  • 我的路线有一个名为“搜索”的参数,其中接收搜索字段的序列化版本,当列表状态第一次加载时,我只使用 this._routeParams.get( 'search'),反序列化过滤器并执行搜索。如果用户通过使用地图或搜索方面更改搜索字段,我只需使用路由器 var 指令的方法 generate = this._router.generate(['Listing',{search: serializedFields}] 构造正确的 url ) 然后使用 this._location.go(instruction.urlPath) 更改 url 而无需重新加载状态。
  • 如果其他人想知道: import { Location } from 'angular2/platform/common';
  • import { Location } from '@angular/common'; 在 Angular 4 中
  • 你有没有像constructor(private location: Location){ }这样的构造函数
  • @AdrianE 问题很可能是您输入了location.go(),而您应该输入了this.location.go()。当您发出 this. 时,您调用 Typescript 的位置接口。
【解决方案2】:

在 Angular2 的 RCx 版本中,我遇到了很大的麻烦。 Location 包已移动,在 constructor() 中运行 location.go() 将不起作用。它需要是 ngOnInit() 或更晚的生命周期。下面是一些示例代码:

import {OnInit} from '@angular/core';
import {Location} from '@angular/common';

@Component({
  selector: 'example-component',
  templateUrl: 'xxx.html'
})
export class ExampleComponent implements OnInit
{
  constructor( private location: Location )
  {}

  ngOnInit()
  {    
    this.location.go( '/example;example_param=917' );
  }
}

以下是有关此事的角度资源: https://angular.io/docs/ts/latest/api/common/index/Location-class.html https://angular.io/docs/ts/latest/api/common/index/LocationStrategy-class.html

【讨论】:

    【解决方案3】:

    从 RC6 开始,您可以执行以下操作来更改 URL 而无需更改状态,从而保留您的路由历史记录

        import {OnInit} from '@angular/core';
    
        import {Location} from '@angular/common'; 
        // If you dont import this angular will import the wrong "Location"
    
        @Component({
            selector: 'example-component',
            templateUrl: 'xxx.html'
        })
        export class ExampleComponent implements OnInit {
            
            constructor( private location: Location )
            {}
    
            ngOnInit() {    
                this.location.replaceState("/some/newstate/");
            }
        }
    

    【讨论】:

    • 这对我不起作用。它会尝试加载路线。控制台错误:Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'some/newstate'
    • 按照@golfadas 的建议将其与创建网址结合起来,我们就有了赢家!
    【解决方案4】:

    对于像我这样发现这个问题的人来说,以下内容可能会有用。

    我遇到了类似的问题,最初尝试使用 location.go 和 location.replaceState,正如此处其他答案中所建议的那样。但是,当我不得不导航到应用程序上的另一个页面时遇到了问题,因为导航是相对于当前路线的,并且当前路线没有被 location.go 或 location.replaceState 更新(路由器什么都不知道关于这些对 URL 的作用)

    本质上,我需要一个解决方案,当路由参数更改时不会重新加载页面/组件,但会在内部更新路由状态。

    我最终使用了查询参数。你可以在这里找到更多信息:https://angular-2-training-book.rangle.io/handout/routing/query_params.html

    因此,如果您需要执行一些操作,例如保存订单并获取订单 ID,您可以更新您的页面 URL,如下所示。在地图上更新中心位置和相关数据是类似的

    // let's say we're saving an order. Initally the URL is just blah/orders
    save(orderId) {
        // [Here we would call back-end to save the order in the database]
    
        this.router.navigate(['orders'], { queryParams: { id: orderId } });
        // now the URL is blah/orders?id:1234. We don't reload the orders
        // page or component so get desired behaviour of not seeing any 
        // flickers or resetting the page.
    }
    

    然后您在 ngOnInit 方法中跟踪它,例如:

    ngOnInit() {
        this.orderId = this.route
            .queryParamMap
            .map(params => params.get('id') || null);
        // orderID is up-to-date with what is saved in database now, or if
        // nothing is saved and hence no id query paramter the orderId variable
        // is simply null.
        // [You can load the order here from its ID if this suits your design]
    }
    

    如果您需要使用新的(未保存的)订单直接进入订单页面,您可以这样做:

    this.router.navigate(['orders']);
    

    或者,如果您需要直接转到现有(已保存)订单的订单页面,您可以这样做:

    this.router.navigate(['orders'], { queryParams: { id: '1234' } });
    

    【讨论】:

    • 确实,这正确地更新了路线(从 Angular 的角度来看)并不会重建 'orders' 的组件,这正是我想要的。
    【解决方案5】:

    使用 location.go(url) 是可行的方法,但不要对 url 进行硬编码,而是考虑使用 router.createUrlTree() 生成它。

    假设您要执行以下路由器调用:this.router.navigate([{param: 1}], {relativeTo: this.activatedRoute}) 但无需重新加载组件,可以将其重写为:

    const url = this.router.createUrlTree([], {relativeTo: this.activatedRoute, queryParams: {param: 1}}).toString()
    
     this.location.go(url);
    

    【讨论】:

    • 这个答案解决了我的问题。一个问题,上面生成的 url 具有由“;”(分号)分隔的参数。我们应该怎么做才能用“&”分隔查询中的每个参数?
    • 这是 createUrlTree(commands: any[], navigationExtras?: NavigationExtras) 的声明。您必须使用位于 navigationExtras 的 queryParams 而不是逗号。 createUrlTree([], {relativeTo: this.activatedRoute, queryParams: {param: 1} })
    • 只是为了澄清@kit 所说的内容,请执行以下操作:this.router.createUrlTree([], {relativeTo: this.activatedRoute, queryParams: {param: 1}}).toString()
    • 这是个好主意,但是location.go this.activatedRoute 不会改变,所以你还要给旧路由添加参数……
    【解决方案6】:

    对我来说,它实际上是 Angular 4.4.5 的混合体。

    使用 router.navigate 不断破坏我的网址,不尊重 realtiveTo:activatedRoute 部分。

    我最终得到了:

    this._location.go(this._router.createUrlTree([this._router.url], { queryParams: { profile: value.id } }).toString())
    

    【讨论】:

      【解决方案7】:

      我用这种方式获取:

      const queryParamsObj = {foo: 1, bar: 2, andThis: 'text'};
      
      this.location.replaceState(
        this.router.createUrlTree(
          [this.locationStrategy.path().split('?')[0]], // Get uri
          {queryParams: queryParamsObj} // Pass all parameters inside queryParamsObj
        ).toString()
      );
      

      -- 编辑--

      我认为我应该为此添加更多信息。

      如果你使用this.location.replaceState()你的应用程序的路由器没有更新,所以如果你以后使用路由器信息它在你的浏览器中是不相等的。例如,如果您使用 localizeService 更改语言,则在切换语言后,您的应用程序会回到您使用 this.location.replaceState() 更改之前的最后一个 URL。

      如果您不希望这种行为,您可以选择不同的方法来更新 URL,例如:

      this.router.navigate(
        [this.locationStrategy.path().split('?')[0]],
        {queryParams: queryParamsObj}
      );
      

      在此选项中,您的浏览器也不会刷新,但您的URL 更改也会注入到您的应用程序的Router,因此当您切换语言时,您不会遇到this.location.replaceState() 中的问题。

      当然,您可以根据需要选择方法。第一个更轻松,因为您只需在浏览器中更改 URL 即可。

      【讨论】:

        【解决方案8】:

        在更改 url 时使用属性 queryParamsHandling: 'merge'。

        this.router.navigate([], {
                queryParams: this.queryParams,
                queryParamsHandling: 'merge',
                replaceUrl: true,
        });
        

        【讨论】:

        • 这会导致当前路由的组​​件被重新加载
        【解决方案9】:

        在我的情况下,我需要删除 url 的查询参数以防止用户看到它。

        我发现replaceState 比 location.go 更安全,因为旧查询参数的路径从堆栈中消失了,用户可以重做与此查询相关的查询。所以,我更喜欢这样做:

        this.location.replaceState(this.router.url.split('?')[0]);

        Whit location.go,使用浏览器返回将返回带有查询参数的旧路径,并将其保留在导航堆栈中。

        this.location.go(this.router.url.split('?')[0]);

        【讨论】:

          【解决方案10】:

          我有类似问题中描述的要求,根据现有答案花了一段时间才弄清楚,所以我想分享我的最终解决方案。

          要求

          我的视图(组件,技术上)的状态可以由用户更改(过滤器设置,排序选项等)当状态发生变化时,即用户更改排序方向,我想:

          • 在 URL 中反映状态变化
          • 处理状态更改,即进行 API 调用以接收新结果集

          另外,我想:

          • 指定是否根据情况在浏览器历史记录(后退/前进)中考虑 URL 更改
          • 使用复杂对象作为状态参数,以便在处理状态更改时提供更大的灵活性(可选,但可以让生活更轻松,例如当某些状态更改触发后端/API 调用而其他由前端内部处理时)

          解决方案:在不重新加载组件的情况下更改状态

          状态变化不会在使用路由参数或查询参数时导致组件重新加载。组件实例保持活动状态。我认为没有充分的理由使用Location.go()location.replaceState() 来弄乱路由器状态。

          var state = { q: 'foo', sort: 'bar' }; 
          var url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: state }).toString();
          this.router.navigateByUrl(url);
          

          state 对象将被 Angular 的 Router 转换为 URL 查询参数:

          https://localhost/some/route?q=foo&sort=bar

          解决方案:处理状态更改以进行 API 调用

          上面触发的状态变化可以通过订阅ActivatedRoute.queryParams来处理:

          export class MyComponent implements OnInit {
          
             constructor(private activatedRoute: ActivatedRoute) { }
          
             ngOnInit()
             {
                this.activatedRoute.queryParams.subscribe((params) => {
                   // params is the state object passed to the router on navigation 
                   // Make API calls here
                });
             }
          
          }
          

          上述例子的state 对象将作为queryParams observable 的params 参数传递。如有必要,可以在处理程序中调用 API。

          但是:我更喜欢直接在我的组件中处理状态更改,并避免绕道ActivatedRoute.queryParams。 IMO,导航路由器,让 Angular 做路由魔术并处理 queryParams 更改以做某事,完全混淆了我的组件中发生的关于我的代码的可维护性和可读性的事情。我会做什么:

          将传入queryParams observable 的状态与我的组件中的当前状态进行比较,如果它在那里没有更改,则什么也不做,而是直接处理状态更改:

          export class MyComponent implements OnInit {
          
             private _currentState;
          
             constructor(private activatedRoute: ActivatedRoute) { }
          
             ngOnInit()
             {
                this.activatedRoute.queryParams.subscribe((params) => {
                   // Following comparison assumes, that property order doesn't change
                   if (JSON.stringify(this._currentState) == JSON.stringify(params)) return;
                   // The followig code will be executed only when the state changes externally, i.e. through navigating to a URL with params by the user
                   this._currentState = params;
                   this.makeApiCalls();
                });
             }
          
             updateView()
             {          
                this.makeApiCalls();
                this.updateUri();
             }    
          
             updateUri()
             {
                var url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: this._currentState }).toString();
          this.router.navigateByUrl(url);
             }
          }
          

          解决方案:指定浏览器历史记录行为

          var createHistoryEntry = true // or false
          var url = ... // see above
          this.router.navigateByUrl(url, { replaceUrl : !createHistoryEntry});
          

          解决方案:将复杂对象作为状态

          这超出了最初的问题,但解决了常见场景,因此可能有用:上面的 state 对象仅限于平面对象(只有简单的字符串/布尔/int/...属性但没有嵌套对象的对象)。我发现了这个限制,因为我需要区分需要通过后端调用处理的属性和其他仅由组件内部使用的属性。我想要一个像这样的状态对象:

          var state = { filter: { something: '', foo: 'bar' }, viewSettings: { ... } };
          

          要将此状态用作路由器的 queryParams 对象,需要将其展平。我只是JSON.stringify对象的所有一级属性:

          private convertToParamsData(data) {
              var params = {};
          
              for (var prop in data) {
                if (Object.prototype.hasOwnProperty.call(data, prop)) {
                  var value = data[prop];
                  if (value == null || value == undefined) continue;
                  params[prop] = JSON.stringify(value, (k, v) => {
                    if (v !== null) return v
                  });
                }
              }
              return params;
           }
          

          然后返回,在处理路由器传入的 queryParams 时:

          private convertFromParamsData(params) {
              var data = {};
          
              for (var prop in params) {
                if (Object.prototype.hasOwnProperty.call(params, prop)) {
                  data[prop] = JSON.parse(params[prop]);
                }
              }
              return data;
          }
          

          最后:一个现成的 Angular 服务

          最后,所有这些都隔离在一个简单的服务中:

          import { Injectable } from '@angular/core';
          import { ActivatedRoute, Router } from '@angular/router';
          import { Observable } from 'rxjs';
          import { Location } from '@angular/common';
          import { map, filter, tap } from 'rxjs/operators';
          
          @Injectable()
          export class QueryParamsService {
          
            private currentParams: any;
          
            externalStateChange: Observable<any>;
          
            constructor(private activatedRoute: ActivatedRoute, private router: Router, private location: Location) {
          
              this.externalStateChange = this.activatedRoute.queryParams
                .pipe(map((flatParams) => {
                  var params = this.convertFromParamsData(flatParams);
                  return params
                }))
                .pipe(filter((params) => {
                  return !this.equalsCurrentParams(params);
                }))
                .pipe(tap((params) => {
                  this.currentParams = params;
                }));
            }
          
            setState(data: any, createHistoryEntry = false) {
              var flat = this.convertToParamsData(data);
              const url = this.router.createUrlTree([], { relativeTo: this.activatedRoute, queryParams: flat }).toString();
              this.currentParams = data;
              this.router.navigateByUrl(url, { replaceUrl: !createHistoryEntry });
            }
          
            private equalsCurrentParams(data) {
              var isEqual = JSON.stringify(data) == JSON.stringify(this.currentParams);
              return isEqual;
            }
          
            private convertToParamsData(data) {
              var params = {};
          
              for (var prop in data) {
                if (Object.prototype.hasOwnProperty.call(data, prop)) {
                  var value = data[prop];
                  if (value == null || value == undefined) continue;
                  params[prop] = JSON.stringify(value, (k, v) => {
                    if (v !== null) return v
                  });
                }
              }
              return params;
            }
          
            private convertFromParamsData(params) {
              var data = {};
          
              for (var prop in params) {
                if (Object.prototype.hasOwnProperty.call(params, prop)) {
                  data[prop] = JSON.parse(params[prop]);
                }
              }
              return data;
            }
          }
          

          可以这样使用:

          @Component({
            selector: "app-search",
            templateUrl: "./search.component.html",
            styleUrls: ["./search.component.scss"],
            providers: [QueryParamsService]
          })
          export class ProjectSearchComponent implements OnInit {
          
              filter : any;
          
              viewSettings : any;
          
              constructor(private queryParamsService: QueryParamsService) { }
          
              ngOnInit(): void {
          
                  this.queryParamsService.externalStateChange
                    .pipe(debounce(() => interval(500))) // Debounce optional
                    .subscribe(params => {
                     // Set state from params, i.e.
                     if (params.filter) this.filter = params.filter;
                     if (params.viewSettings) this.viewSettings = params.viewSettings;
          
                     // You might want to init this.filter, ... with default values here
                     // If you want to write default values to URL, you can call setState here
                      this.queryParamsService.setState(params, false); // false = no history entry
          
                      this.initializeView(); //i.e. make API calls        
                   });
               }
          
               updateView() {
          
                 var data = {
                   filter: this.filter,
                   viewSettings: this.viewSettings
                 };
          
                 this.queryParamsService.setState(data, true);
          
                 // Do whatever to update your view
               }
          
            // ...
          
          }
          

          不要忘记组件级别的providers: [QueryParamsService] 语句为组件创建新的服务实例。不要在应用模块上全局注册服务。

          【讨论】:

            【解决方案11】:

            这是我在 2021 年使用的解决方案。使用 createUrlTree 创建 URL 树并使用 location 导航到路由

            //Build URL Tree
                const urlTree = this.router.createUrlTree(["/employee/"+this.employeeId],{
                  relativeTo: this.route,
                  queryParams: params,
                  queryParamsHandling: 'merge'
                });
            
            //Update the URL 
            this.location.go(urlTree.toString());
            

            【讨论】:

            • 在 Angular 12 中进行了检查,它就像一个魅力。只有我使用 replaceState 方法而不是 go 以便它替换以前的状态而不是添加到它。当您的 URL 以 /new 结尾,然后您想在将其保存到 DB 后用实体 ID 替换 new 时非常方便。
            【解决方案12】:

            如果您不想在URL参数更改时调用API,最好使用activatedRoute.navigate()更改URL参数并使用快照(不是订阅)调用API。

            export class MyComponent implements OnInit {
            
               constructor(private activatedRoute: ActivatedRoute) { }
            
               ngOnInit()
               {
                  const params = this.activatedRoute.snapshot.queryParams;
                     // params is the state object passed to the router on navigation 
                     // Make API calls here
               }
            
            }
            

            【讨论】:

              【解决方案13】:
              import { Component, OnInit } from '@angular/core';
              import { Location } from '@angular/common';
              
              @Component({
                  selector: 'child-component',
                  templateUrl: 'child.component.html',
                  styleUrls: ['child.component.scss']
              })
              export class ChildComponent implements OnInit {
                  
                  constructor(
                     private location: Location
                  ) {}
              
                  ngOnInit() {
                      // you can put 'this.location.go()' method call in any another method
                      this.location.go('parentRoute/anotherChildRoute');
                  }
              }
              

              对我来说,它改变了浏览器中的子路由,而不需要重新加载任何当前组件。

              【讨论】:

                猜你喜欢
                • 1970-01-01
                • 2017-05-14
                • 2016-12-22
                • 1970-01-01
                • 2016-08-31
                • 2018-04-05
                • 2018-03-20
                • 1970-01-01
                • 2017-01-17
                相关资源
                最近更新 更多