改变路线参数,不重新装载在角度2

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

219402 次浏览

您可以使用 location.go(url),它将基本上改变您的网址,而不改变应用程序的路由。

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

描述 location.go的相关问题 不会对 Router发生亲密变化。

我有很大的麻烦让这个工作在 RCx 版本的 angular2。Location 包已经移动,在 structor ()内部运行 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

从 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/");
}
}

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

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

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

我最终使用了查询参数

因此,如果您需要做一些事情,如保存订单,并得到一个订单 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' } });

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

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

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


this.location.go(url);

对我来说,它实际上是两者的混合角度4.4.5。

通过不尊重 realtiveTo: activatedRoute 部分,使用 router.Navin 不断地破坏我的 URL。

最后我得到了:

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

我用这种方法得到它:

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。

如果你不想要这种行为,你可以选择不同的方法来更新网址,如:

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

在这个选项中,你的浏览器也不会刷新,但是你的 URL变化也会被注入到你的应用程序的 Router中,所以当你切换语言的时候,你不会遇到像 this.location.replaceState()那样的问题。

当然,你可以根据自己的需要选择方法。第一种方法更轻便,因为与在浏览器中更改 URL相比,不需要更多地使用应用程序。

在更改 url 时使用属性 queryParamsProcessing: ‘ merge’。

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

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

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

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

返回与浏览器将返回到您的查询参数的旧路径,并将它保留在导航堆栈。

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

我有类似的要求,在问题中描述,它需要一段时间来弄清楚事情的基础上,现有的答案,所以我想分享我的最终解决方案。

规定

我的视图的状态(技术上来说是组件)可以被用户改变(过滤器设置,排序选项等)当状态改变发生时,比如用户改变排序方向,我想:

  • 反映 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可观察对象的 params参数传递。如果需要,可以在处理程序中进行 API 调用。

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

将传递给 queryParams的状态与组件中的当前状态进行比较,如果组件中的状态没有改变,那么什么也不做,而是直接处理状态改变:

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对象仅限于平面对象(一个只有简单的字符串/bool/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;
}

最后: 一个现成的使用角服务

最后,所有这些都被孤立在一个简单的服务中:

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]语句为组件创建一个新的服务实例。不要在应用程序模块上全局注册服务。

在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());

如果你不想在 URL 参数发生变化时调用 API,那么最好使用 activatedRoute.导航()来更改 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
}


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

对我来说,它改变浏览器中的子路由,没有任何当前的组件重新加载。

我正在尝试更新查询参数和导航而不重新加载。activatedRoute.snapshot.queryparams本质上是只读的。这种转变的方法解决了我的问题。

// Get queryparams
let state = Object.assign({}, this.route.snapshot.queryParams)


// Change parameters of url
state["z"] = "hi";
state["y"] = "bye";


// Create url and navigate to it without reloading
const url = this.router.createUrlTree([], { relativeTo: this.route, queryParams: state }).toString();
this.router.navigateByUrl(url);