Skip to main content
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Christine Dohmen
Ben Nadel at cf.Objective() 2013 (Bloomington, MN) with: Christine Dohmen

Changing The Hash With The Location Service In Angular 4.4.0-RC.0

By
Published in Comments (3)

The Angular framework provides two ways of working with the browser location: the Location service for direct URL manipulation; and, the Router for more advanced URL manipulation, parsing, and view rendering. In an application, I would use the Router; but, in my Angular demos, the Router is way overkill; so I often reach for the Location service. Now, one thing that's not obvious in the Location documentation is how to change the location Hash or fragment. As it turns out, you can include the hash in the "path" argument when navigating (as long as you also include the optional query-string in the path as well).

Run this demo in my JavaScript Demos project on GitHub.

The Location service in Angular 4.4 has a .go() method with the following signature:

go( path: string, query: string ): void

To look at this, one would logically have to draw the conclusion that there is no way to change the hash or fragment portion of the application URL. After all, if we're breaking query-string apart from the path, then it would stand to reason there would also be a "hash" argument if the hash could be changed programmatically.

If you look under the hood, however, you'll see that the LocationStrategy implementations in Angular (Hash and Path) are simply normalizing and concatenating the path and the query-string before passing them through to the underlying platform location. Roughly speaking, the implementations look like this:

let url = ( path + Location.normalizeQueryParams( queryParams ) );

This means that if the "queryParams" argument is empty, "path" becomes the value that's passed to the underlying platform implementation. Which means, the "path" argument should be able to contain the pathname, the query-string, and the hash. And, in fact, if you treat the "path" argument as the entire application URL, omitting the "query" argument altogether, you can use the .go() method to change the all three segments of the location path, including the fragment.

To see this in action, I've put together a rather trite demo that passes full, hash-containing URLs into the location.go() method and then queries the location to see if the changes took place:

// Import the core angular services.
import { Component } from "@angular/core";
import { Location } from "@angular/common";
import { PopStateEvent } from "@angular/common";

@Component({
	selector: "my-app",
	styleUrls: [ "./app.component.css" ],
	template:
	`
		<h2>
			HREF-Based URL Changes
		</h2>

		<ul>
			<li>
				<a href="#/route-one?hello=world#hashification">
					#/route-one?hello=world#hashification
				</a>
			</li>
			<li>
				<a href="#/route-two?cool=beans#hashitation">
					#/route-two?cool=beans#hashitation
				</a>
			</li>
		</ul>

		<h2>
			Location-Based URL Changes
		</h2>

		<ul>
			<li>
				<a (click)="navigate( '/route-three?meep=moop#hashmatash' )">
					Route three
				</a>
			</li>
			<li>
				<a (click)="navigate( '/route-four?king=kong#hashmania' )">
					Route four
				</a>
			</li>
		</ul>
	`
})
export class AppComponent {

	public location: Location;

	// I initialize the app component.
	constructor( location: Location ) {

		this.location = location;
		// While the PopStateEvent won't trigger when we call location.go(), it will
		// trigger when we use the HREF-based navigation. Let's listen for those location
		// changes that occur due to external changes in the browser URL.
		this.location.subscribe(
			( event: PopStateEvent ) : void => {

				if ( event.type === "hashchange" ) {

					console.group( "PopState" );
					console.log( event.url );
					console.groupEnd();

				}

			}
		);

	}

	// ---
	// PUBLIC METHODS.
	// ---

	// I navigate to the given URL. This URL is expected to be a "complete" URL; meaning,
	// it contains all the necessary components: pathname, query-string, and hash.
	public navigate( newPath: string ) : void {

		// In the documentation, the .go() method accepts two arguments: "path" and
		// "query". This leaves you wondering about the "hash" - where does that go?
		// Well, it turns out that the path and query don't really need to be broken out
		// into separate components. Under the hood, they are just concatenated. As such,
		// we can include them in the "path" argument, along with any desired HASH value.
		this.location.go( newPath );

		// Since the PopStateEvent doesn't fire when we programmatically navigate, let's
		// turn around and query the location.
		console.group( "Internal Navigation" );
		console.log( this.location.path() );
		console.groupEnd();

	}

}

As you can see, I'm including two HREF-based links as a control-test alongside two (click)-based links. In my (click)-based links, though, you can see that the value being passed to location.go() contains a comprehensive URL that includes the pathname, the query-string, and the hash. And, if we click through these URLs, we can see that the hash is properly set using location.go():

Changing the application url hash / fragment using the location service in Angular 4.

This works quite nicely. But, one word of caution: once you start putting full URLs in the "path" argument, you have to omit the "query" argument. If you try to use the query argument, you may end up inserting the query-string after the embedded hash, which will not work.

For completeness, I will say that could also include the hash inside the query-string, such that your location.go() method looks like this:

location.go( "path.htm", "foo=bar**#hash**" )

This works because the values are, ultimately, just concatenated under the hood; however, this feels a bit more janky. I'd rather have one overloaded "path" argument than try to overload the "query" argument.

If you're building an Angular application, you'll likely end up using the Router; however, in cases where you want to use the Location service directly, it may not be clear how to change the hash / fragment. Luckily, if we just put the pathname, query-string, and fragment inside the "path" argument, then location.go() will change the location hash.

Want to use code from this post? Check out the license.

Reader Comments

15,848 Comments

@All,

While working on this post, I was feeling very nostalgic for the $location service in Angular 1.x - particularly the ability to access and mutate portions of the URL independently (ex, path, search, hash). As such, I tried to re-create some of that goodness:

www.bennadel.com/blog/3332-creating-an-angular-1-x-location-inspired-retrolocation-service-in-angular-4-4-0-rc-0.htm

I created a "RetroLocation" service that works on top of the LocationStrategy implementation in an Angular 4.x application.

7 Comments

Just want to say howdy, Ben! Just stumbled across this looking for some url strategies. Hope all is well for you these days!

15,848 Comments

@Dave,

Good sir, it's been a while :D Things are well. Not sure if this post was actually helpful -- but, it was nice to hear from you.

I believe in love. I believe in compassion. I believe in human rights. I believe that we can afford to give more of these gifts to the world around us because it costs us nothing to be decent and kind and understanding. And, I want you to know that when you land on this site, you are accepted for who you are, no matter how you identify, what truths you live, or whatever kind of goofy shit makes you feel alive! Rock on with your bad self!
Ben Nadel