我正在使用 Angular 2、谷歌地图等制作一个房地产网站,当用户更改地图的中心时,我对 API 执行搜索,指示地图的当前位置以及半径。问题是,我想在不重新加载整个页面的情况下在 url 中反映这些值。那可能吗?我找到了一些使用 AngularJS 1.x 的解决方案,但没有找到关于 Angular 2 的解决方案。
从 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(url)
,它基本上会更改您的网址,而不会更改应用程序的路线。
注意这可能会导致其他影响,例如从当前路由重定向到子路由。
描述 location.go
的 Related question 不会提示 Router
发生更改。
import { Location } from '@angular/common';
在 Angular 4 中
constructor(private location: Location){ }
这样的构造函数
location.go()
而您应该输入了 this.location.go()
。当您发出 this.
时,您调用 Typescript 的位置接口。
使用 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);
this.router.createUrlTree([], {relativeTo: this.activatedRoute, queryParams: {param: 1}}).toString()
location.go
this.activatedRoute 不会改变,因此您还将向旧路由添加参数...
对于像我这样发现这个问题的人来说,以下内容可能会有用。
我遇到了类似的问题,最初尝试使用 location.go 和 location.replaceState,如此处其他答案中所建议的那样。但是,当我不得不导航到应用程序上的另一个页面时遇到了问题,因为导航是相对于当前路线的,并且当前路线没有被 location.go 或 location.replaceState 更新(路由器什么都不知道关于这些对 URL 的作用)
本质上,我需要一个解决方案,当路由参数更改但 DID 在内部更新路由状态时不会重新加载页面/组件。
我最终使用了查询参数。您可以在此处找到更多信息: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' } });
'orders'
处重建组件,这正是我想要的。
我很难让它在 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
我用这种方式得到它:
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
即可。
我有类似问题中描述的要求,根据现有答案花了一段时间才弄清楚,所以我想分享我的最终解决方案。
要求
我的视图状态(组件,技术上)可以由用户更改(过滤器设置,排序选项等)当状态发生变化时,即用户更改排序方向,我想:
反映 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);
Angular 的 Router
将 state
对象转换为 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
});
}
}
上述 axample 的 state
对象将作为 queryParams
observable 的 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
对象仅限于平面对象(只有简单的字符串/布尔/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]
语句为组件创建新的服务实例。不要在 app 模块上全局注册服务。
在更改 url 时使用属性 queryParamsHandling: 'merge'。
this.router.navigate([], {
queryParams: this.queryParams,
queryParamsHandling: 'merge',
replaceUrl: true,
});
对我来说,它实际上是两者与 Angular 4.4.5 的混合。
使用 router.navigate 通过不尊重 realtiveTo:activatedRoute 部分来不断破坏我的网址。
我最终得到:
this._location.go(this._router.createUrlTree([this._router.url], { queryParams: { profile: value.id } }).toString())
在 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());
replaceState
方法而不是 go
以便它替换以前的状态而不是添加到它。当您有以 /new
结尾的 URL,然后您想在将 new
保存到 DB 后将其替换为实体 ID 时,这很方便。
在我的情况下,我需要删除 url 的查询参数以防止用户看到它。
我发现 replaceState
比 location.go 更安全,因为旧查询参数的路径从堆栈中消失了,用户可以重做与此查询相关的查询。所以,我更喜欢这样做:
this.location.replaceState(this.router.url.split('?')[0]);
Whit location.go
,返回浏览器将使用查询参数返回您的旧路径,并将其保留在导航堆栈中。
this.location.go(this.router.url.split('?')[0]);
如果您不想在 URL 参数更改时调用 API,最好使用 activateRoute.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
}
}
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);
Error: Uncaught (in promise): Error: Cannot match any routes. URL Segment: 'some/newstate'