New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

router should default to hash location #4735

Closed
simpulton opened this Issue Oct 14, 2015 · 47 comments

Comments

Projects
None yet
@simpulton
Contributor

simpulton commented Oct 14, 2015

Most build systems (other than webpack) HTML5 push state is not enabled by default.

This will most definitely cause confusion and frustration for the majority of developers especially if they are using a live reload mechanism.

@btford

This comment has been minimized.

Show comment
Hide comment
@btford

btford Oct 16, 2015

Contributor

Open to changing this. Not sure what build systems have to do with this– shouldn't this only be a server issue?

Can you give an example of a setup where this is problematic?

@IgorMinar @matsko what do you think?

Contributor

btford commented Oct 16, 2015

Open to changing this. Not sure what build systems have to do with this– shouldn't this only be a server issue?

Can you give an example of a setup where this is problematic?

@IgorMinar @matsko what do you think?

@IgorMinar

This comment has been minimized.

Show comment
Hide comment
@IgorMinar

IgorMinar Oct 16, 2015

Member

In A1 we defaulted to hash location and many people didn't even know that there was another option.

In A2, we should stick to pushState by default unless there is an overwhelming evidence that this is problematic. I'd rather work on exposing developers to the correct serve side configuration (or provide it out of the box wherever we can)

Member

IgorMinar commented Oct 16, 2015

In A1 we defaulted to hash location and many people didn't even know that there was another option.

In A2, we should stick to pushState by default unless there is an overwhelming evidence that this is problematic. I'd rather work on exposing developers to the correct serve side configuration (or provide it out of the box wherever we can)

@rkirov

This comment has been minimized.

Show comment
Hide comment
@rkirov

rkirov Oct 16, 2015

Contributor

Do you know of any simple webservers that support mapping app/any/custom/route/here to app/index.html with minimal configuration? Something like python -m SimpleHTTPServer?

Contributor

rkirov commented Oct 16, 2015

Do you know of any simple webservers that support mapping app/any/custom/route/here to app/index.html with minimal configuration? Something like python -m SimpleHTTPServer?

@juergenzimmermann

This comment has been minimized.

Show comment
Hide comment
@simpulton

This comment has been minimized.

Show comment
Hide comment
@simpulton

simpulton Oct 16, 2015

Contributor

I do not think we should force a backend concern on a frontend framework. The only way to get push state to work is by configuring a server which could involve any number of permutations and / or concerns. There is no unified way to do this with a one size fits all solution.

Forcing a developer to not only know Angular but ALSO how to configure push state on a server is just one more barrier to entry. I am a strong proponent to easy mode by default with plenty of room for power
through easy configuration.

Contributor

simpulton commented Oct 16, 2015

I do not think we should force a backend concern on a frontend framework. The only way to get push state to work is by configuring a server which could involve any number of permutations and / or concerns. There is no unified way to do this with a one size fits all solution.

Forcing a developer to not only know Angular but ALSO how to configure push state on a server is just one more barrier to entry. I am a strong proponent to easy mode by default with plenty of room for power
through easy configuration.

@pkozlowski-opensource

This comment has been minimized.

Show comment
Hide comment
@pkozlowski-opensource

pkozlowski-opensource Oct 16, 2015

Member

I'm with @simpulton on this. The less friction for newcomers the better. In my experience part of the Angular 1 popularity could get started with it quickly and experiment with the framework without too much fuss.

In A2, we should stick to pushState by default unless there is an overwhelming evidence that this is problematic. I'd rather work on exposing developers to the correct serve side configuration (or provide it out of the box wherever we can)

@IgorMinar IMO what is "correct" is debatable - if you need to support older browsers you can't use push state anyway. And with such great diversity of web servers being used in dev / prod, I'm not sure we want to go into business of hand-holding people when it comes to server-side config.

Member

pkozlowski-opensource commented Oct 16, 2015

I'm with @simpulton on this. The less friction for newcomers the better. In my experience part of the Angular 1 popularity could get started with it quickly and experiment with the framework without too much fuss.

In A2, we should stick to pushState by default unless there is an overwhelming evidence that this is problematic. I'd rather work on exposing developers to the correct serve side configuration (or provide it out of the box wherever we can)

@IgorMinar IMO what is "correct" is debatable - if you need to support older browsers you can't use push state anyway. And with such great diversity of web servers being used in dev / prod, I'm not sure we want to go into business of hand-holding people when it comes to server-side config.

@btford

This comment has been minimized.

Show comment
Hide comment
@btford

btford Oct 23, 2015

Contributor

I do not think we should force a backend concern on a frontend framework.

Right but the same could be said for the backend forcing this front-end behavior.

We should try to improve the tooling first. I think @johnpapa implemented something like http-server but with support for "HTML5-style" history. Lets get tools in a good place, let people spend some time with the tools, and then come back to this issue when we have a better understanding of it.

Contributor

btford commented Oct 23, 2015

I do not think we should force a backend concern on a frontend framework.

Right but the same could be said for the backend forcing this front-end behavior.

We should try to improve the tooling first. I think @johnpapa implemented something like http-server but with support for "HTML5-style" history. Lets get tools in a good place, let people spend some time with the tools, and then come back to this issue when we have a better understanding of it.

@matsko

This comment has been minimized.

Show comment
Hide comment
@matsko

matsko Oct 23, 2015

Member

I'm with @simpulton and @pkozlowski-opensource on this. It's a nice default to have when adding routing since it doesn't give a "page refresh surprise" for when HTML5 is enabled.

Member

matsko commented Oct 23, 2015

I'm with @simpulton and @pkozlowski-opensource on this. It's a nice default to have when adding routing since it doesn't give a "page refresh surprise" for when HTML5 is enabled.

@johnpapa

This comment has been minimized.

Show comment
Hide comment
@johnpapa

johnpapa Oct 23, 2015

Contributor

Most folks will use a real server and they won't have this issue, as they will code for it. So this is really about onboarding and the friction with it

I understand and have felt the friction when I share ng2 with folks. But I think a better option is tooling, as Brian said. We want folks to use html5 mode.

I wrote a very simple server called lite-server which runs browser-sync to launch the static server, inject CSS, and refresh when watched files change. It also relies on connect-history-api-fallback which handles the issue mentioned here. Since using this, I have had a much easier time getting folks on board.

I'm not saying that lite-server is the best way, but I do think a simple server will make this much easier and still drive folks to html5 mode.

https://www.npmjs.com/package/lite-server

Contributor

johnpapa commented Oct 23, 2015

Most folks will use a real server and they won't have this issue, as they will code for it. So this is really about onboarding and the friction with it

I understand and have felt the friction when I share ng2 with folks. But I think a better option is tooling, as Brian said. We want folks to use html5 mode.

I wrote a very simple server called lite-server which runs browser-sync to launch the static server, inject CSS, and refresh when watched files change. It also relies on connect-history-api-fallback which handles the issue mentioned here. Since using this, I have had a much easier time getting folks on board.

I'm not saying that lite-server is the best way, but I do think a simple server will make this much easier and still drive folks to html5 mode.

https://www.npmjs.com/package/lite-server

@dsebastien

This comment has been minimized.

Show comment
Hide comment
@dsebastien

dsebastien Oct 23, 2015

+1 for @simpulton's idea; it took me a bit of time before finding a solution during development. I think it's better to have the SPA-friendly default built in Angular.

Just for the record, the easiest solution I've found to that issue was to combine BrowserSync with the 'connect-history-api-fallback' plugin (https://www.npmjs.com/package/connect-history-api-fallback).

Used like that: https://github.com/dsebastien/modernWebDevBuild/blob/master/src/gulp/tasks/serve.js#L67-L68

dsebastien commented Oct 23, 2015

+1 for @simpulton's idea; it took me a bit of time before finding a solution during development. I think it's better to have the SPA-friendly default built in Angular.

Just for the record, the easiest solution I've found to that issue was to combine BrowserSync with the 'connect-history-api-fallback' plugin (https://www.npmjs.com/package/connect-history-api-fallback).

Used like that: https://github.com/dsebastien/modernWebDevBuild/blob/master/src/gulp/tasks/serve.js#L67-L68

@gkalpak

This comment has been minimized.

Show comment
Hide comment
@gkalpak

gkalpak Oct 26, 2015

Member

👍 for defaulting to no need for backend tweaking (aka hash location).

Even if there are tools that are easy to set up, I don't want to have to use an easy-to-setup tool, I want to use my tool (and not having to worry about setting up backend stuff just to spin off a new Angular-based prototype).

Member

gkalpak commented Oct 26, 2015

👍 for defaulting to no need for backend tweaking (aka hash location).

Even if there are tools that are easy to set up, I don't want to have to use an easy-to-setup tool, I want to use my tool (and not having to worry about setting up backend stuff just to spin off a new Angular-based prototype).

@pkozlowski-opensource

This comment has been minimized.

Show comment
Hide comment
@pkozlowski-opensource

pkozlowski-opensource Oct 30, 2015

Member

Example of the confusion with the current defaults: #5026

Member

pkozlowski-opensource commented Oct 30, 2015

Example of the confusion with the current defaults: #5026

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Nov 3, 2015

@gkalpak I strongly disagree

In practice, Angular2 will be used mostly by front-end devs who don't necessarily have the skills/knowledge to setup a backend.

Why not embrace this as an opportunity to improve the toolchain and lower the barrier-of-entry to newcomers to SPA development?

Use-case:


Lets say there are separate front-end and back-end development teams working in parallel to build a MVP.

Back-end is responsible for:

  • '/' - routes that (eventually) will map to the angular routes
  • '/api/*` - a backend microservice API to manage the persistent datastore

Front-end is responsible:

  • defining the client-side routing
  • fetching data from the API dynamically via Angular+AJAX

Assumption: the client-side routes will be in constant flux as the front-end devs add additional features.

With this scheme, every time the front-end team adds a new client-side route, they won't be able to

test it until the back-end maps the route on the server-side.

Issues:

  • the back-end team is interrupted every time the front-end team adds a new route
  • front-end team is stalled while they wait for the back-end to map the route

In an ideal scenario, both teams should be able to work independently until the routing structure is established and the front-end/back-end are integrated.


Solution: the front-end devs use a testing server configured to redirect all /* requests to /#/*

Since live-reload is the testing server mentioned in the Angular.io docs, I forked it and added this feature.

Specifying the --spa flag 302 (ie temporary/no cache) redirects all requests to root with prepended by a hash.

Example:

live-reload --spa

I'm waiting to see if the project dev is willing to accept the merge. ref tapio/live-server#64

Two questions, does this work with PathLocationStrategy? Should the redirect prepend with a # or a #!?

evanplaice commented Nov 3, 2015

@gkalpak I strongly disagree

In practice, Angular2 will be used mostly by front-end devs who don't necessarily have the skills/knowledge to setup a backend.

Why not embrace this as an opportunity to improve the toolchain and lower the barrier-of-entry to newcomers to SPA development?

Use-case:


Lets say there are separate front-end and back-end development teams working in parallel to build a MVP.

Back-end is responsible for:

  • '/' - routes that (eventually) will map to the angular routes
  • '/api/*` - a backend microservice API to manage the persistent datastore

Front-end is responsible:

  • defining the client-side routing
  • fetching data from the API dynamically via Angular+AJAX

Assumption: the client-side routes will be in constant flux as the front-end devs add additional features.

With this scheme, every time the front-end team adds a new client-side route, they won't be able to

test it until the back-end maps the route on the server-side.

Issues:

  • the back-end team is interrupted every time the front-end team adds a new route
  • front-end team is stalled while they wait for the back-end to map the route

In an ideal scenario, both teams should be able to work independently until the routing structure is established and the front-end/back-end are integrated.


Solution: the front-end devs use a testing server configured to redirect all /* requests to /#/*

Since live-reload is the testing server mentioned in the Angular.io docs, I forked it and added this feature.

Specifying the --spa flag 302 (ie temporary/no cache) redirects all requests to root with prepended by a hash.

Example:

live-reload --spa

I'm waiting to see if the project dev is willing to accept the merge. ref tapio/live-server#64

Two questions, does this work with PathLocationStrategy? Should the redirect prepend with a # or a #!?

@gkalpak

This comment has been minimized.

Show comment
Hide comment
@gkalpak

gkalpak Nov 4, 2015

Member

@evanplaice, I am not sure what on what part you disagree with me.
I didn't say I am against improving tools (I am all for it in fact), I just said, that I think defaulting to HashLocationStrategy would lower the barrier of entry; especially for newcomers (and no matter how much the tools are improved, it will always be easier to use the HashLocationStrategy, so that should be the default).

Of course, switching to PushstateLocationStrategy (or whatever it's called these days 😁) should (and is indeed) pretty easy, so people that know their way around can use either strategy with minimal configuration/boilerplate.)

TBH, I don't feel there is a big difference, but if I had to choose one as default, it'd be HashLocationStrategy.

Member

gkalpak commented Nov 4, 2015

@evanplaice, I am not sure what on what part you disagree with me.
I didn't say I am against improving tools (I am all for it in fact), I just said, that I think defaulting to HashLocationStrategy would lower the barrier of entry; especially for newcomers (and no matter how much the tools are improved, it will always be easier to use the HashLocationStrategy, so that should be the default).

Of course, switching to PushstateLocationStrategy (or whatever it's called these days 😁) should (and is indeed) pretty easy, so people that know their way around can use either strategy with minimal configuration/boilerplate.)

TBH, I don't feel there is a big difference, but if I had to choose one as default, it'd be HashLocationStrategy.

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Nov 11, 2015

@gkalpak I think I may have misinterpreted your last statement. We may be saying the same thing in different ways.

With either LocationStrategy, you'll still need a backend that's tweaked to properly reroute incoming requests.

Concerning the client-side:

I think it's perfectly acceptable to make PathLocationStrategy the default. HashLocationStrategy was a necessary concession for Angular1 because it was released prior to HTML5 pushState().

Now that pushState() is the norm, relying on HashLocationStrategy seems more like a kludge than anything. IMHO, Keep it around for legacy support but Angular2 should reflect the current state of technology and make PathLocationStrategy the default.

Concerning the server-side:

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side. All inbound requests to anything other than root have to be restructured to work with angular and redirected to root.

As I mentioned before, I already have an extension ready for live-server I just need a little clarification to ensure it'll work with Angular2.

  • when an inbound request is redirected to root from the server, does Angular2 detect the hash URL and follow the hashed path during bootstrapping?
  • does the PathLocationStragegy URL format use a single # (hash) or a #! (hash-bang)?
  • how is a hashed path that includes an anchor link handled, does the anchor need to be encoded during the redirect?

evanplaice commented Nov 11, 2015

@gkalpak I think I may have misinterpreted your last statement. We may be saying the same thing in different ways.

With either LocationStrategy, you'll still need a backend that's tweaked to properly reroute incoming requests.

Concerning the client-side:

I think it's perfectly acceptable to make PathLocationStrategy the default. HashLocationStrategy was a necessary concession for Angular1 because it was released prior to HTML5 pushState().

Now that pushState() is the norm, relying on HashLocationStrategy seems more like a kludge than anything. IMHO, Keep it around for legacy support but Angular2 should reflect the current state of technology and make PathLocationStrategy the default.

Concerning the server-side:

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side. All inbound requests to anything other than root have to be restructured to work with angular and redirected to root.

As I mentioned before, I already have an extension ready for live-server I just need a little clarification to ensure it'll work with Angular2.

  • when an inbound request is redirected to root from the server, does Angular2 detect the hash URL and follow the hashed path during bootstrapping?
  • does the PathLocationStragegy URL format use a single # (hash) or a #! (hash-bang)?
  • how is a hashed path that includes an anchor link handled, does the anchor need to be encoded during the redirect?
@pkozlowski-opensource

This comment has been minimized.

Show comment
Hide comment
@pkozlowski-opensource

pkozlowski-opensource Nov 11, 2015

Member

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side.

You have to have a server configured in a way that it redirects to your entry point (index.html) for 404 requests. This is the crux of the issue. IMO we shouldn't force people to mess they server-side config to try out client-side framework.

I'm glad that people got extensions to they favourite little pet-server but please, don't force those things on everyone....

Member

pkozlowski-opensource commented Nov 11, 2015

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side.

You have to have a server configured in a way that it redirects to your entry point (index.html) for 404 requests. This is the crux of the issue. IMO we shouldn't force people to mess they server-side config to try out client-side framework.

I'm glad that people got extensions to they favourite little pet-server but please, don't force those things on everyone....

@gkalpak

This comment has been minimized.

Show comment
Hide comment
@gkalpak

gkalpak Nov 12, 2015

Member

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side. All inbound requests to anything other than root have to be restructured to work with angular and redirected to root.

In case it wasn't clear, as @pkozlowski-opensource said, HashLocationStrategy usually doesn't require any server-side config (since servers ignore the hash of the URL).
PathLocationStrategy, on the other hand, does require server-side config (and this is undesirable imo).

Member

gkalpak commented Nov 12, 2015

HashLocationStragety vs PathLocationStrategy is irrelevant on the server-side. All inbound requests to anything other than root have to be restructured to work with angular and redirected to root.

In case it wasn't clear, as @pkozlowski-opensource said, HashLocationStrategy usually doesn't require any server-side config (since servers ignore the hash of the URL).
PathLocationStrategy, on the other hand, does require server-side config (and this is undesirable imo).

@examdotcom

This comment has been minimized.

Show comment
Hide comment
@examdotcom

examdotcom Jan 16, 2016

PathLocationStrategy is cleaner for linking, indexing, and look, but it will require settings on the server side. It would be nice if Angular could intercept the routes at the browser, but if that is not possible then I would recommend adding sample configurations for common webservers (Apache, Nginx) to the documentation and tutorials to avoid a lot of developer frustration.

examdotcom commented Jan 16, 2016

PathLocationStrategy is cleaner for linking, indexing, and look, but it will require settings on the server side. It would be nice if Angular could intercept the routes at the browser, but if that is not possible then I would recommend adding sample configurations for common webservers (Apache, Nginx) to the documentation and tutorials to avoid a lot of developer frustration.

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Jan 16, 2016

@examdotcom It is, in fact possible to use Angular2 + PathLocationStrategy and handle the redirect on the client-side.

I'm using that exact setup to host my personal site on S3:
evanplaice.com + source

The major barrier is handling deep links as the application needs to bootstrap from index.html before it can consume the path. To make it work you have to redirect to index.html and rewrite the remainder of the path as a hash fragment.

For example:

evanplaice.com/projects -> evanplaice.com/#/projects

Then in your routing component the hash fragment can be used to redirect to the correct route:

export class MainComponent {
  constructor (@Inject(Location) location) {
    resolveHashURL(location);
  }
}

function resolveHashURL (location) {
  let hash = location.platformStrategy._platformLocation.hash;
  if (hash) {
    let path = hash.substring(1);
    // console.log('RedirectTo: ' + path);
    location.go(path);
  }
}

Note:
Keep in mind that the above is a brittle solution. I'm positive it'll break on second (or deeper) tier routes, aux routes, etc. Handling more complex routing would require disassembling the hashurl and feeding it piecemeal to each layer of routing. Browser history would also need to be hacked to overwrite the redirects with clean paths.

I already created an #6127 to address this. From what I gathered, this definitely an exceptional use case so it wouldn't make sense to bloat PathLocationStrategy with the additional code.

I suggest you save yourself the trouble and just use HashLocationStrategy.

evanplaice commented Jan 16, 2016

@examdotcom It is, in fact possible to use Angular2 + PathLocationStrategy and handle the redirect on the client-side.

I'm using that exact setup to host my personal site on S3:
evanplaice.com + source

The major barrier is handling deep links as the application needs to bootstrap from index.html before it can consume the path. To make it work you have to redirect to index.html and rewrite the remainder of the path as a hash fragment.

For example:

evanplaice.com/projects -> evanplaice.com/#/projects

Then in your routing component the hash fragment can be used to redirect to the correct route:

export class MainComponent {
  constructor (@Inject(Location) location) {
    resolveHashURL(location);
  }
}

function resolveHashURL (location) {
  let hash = location.platformStrategy._platformLocation.hash;
  if (hash) {
    let path = hash.substring(1);
    // console.log('RedirectTo: ' + path);
    location.go(path);
  }
}

Note:
Keep in mind that the above is a brittle solution. I'm positive it'll break on second (or deeper) tier routes, aux routes, etc. Handling more complex routing would require disassembling the hashurl and feeding it piecemeal to each layer of routing. Browser history would also need to be hacked to overwrite the redirects with clean paths.

I already created an #6127 to address this. From what I gathered, this definitely an exceptional use case so it wouldn't make sense to bloat PathLocationStrategy with the additional code.

I suggest you save yourself the trouble and just use HashLocationStrategy.

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Feb 3, 2016

I'm particularly interested in the PathLocationStrategy, especiall regarind the question posed by @evanplaice

"how is a hashed path that includes an anchor link handled, does the anchor need to be encoded during the redirect?"

Right now the url rewrite is working but facing the same issue describe here in angular 1

angular/angular.js#5519

The href goes back to the root, no idea about the multiple events generated described in the original issue. For sure, if we decide to keep PathLocationStrategy as the default we have to document how to make it work. Even if the url rewrite might be a more common thing to setup in apache/nginx, I'm a little stump on this hash issue since the resolution in angular1 is not applicable for angular2. If anyone knows how to solve this please let me know. Thanks.

user414 commented Feb 3, 2016

I'm particularly interested in the PathLocationStrategy, especiall regarind the question posed by @evanplaice

"how is a hashed path that includes an anchor link handled, does the anchor need to be encoded during the redirect?"

Right now the url rewrite is working but facing the same issue describe here in angular 1

angular/angular.js#5519

The href goes back to the root, no idea about the multiple events generated described in the original issue. For sure, if we decide to keep PathLocationStrategy as the default we have to document how to make it work. Even if the url rewrite might be a more common thing to setup in apache/nginx, I'm a little stump on this hash issue since the resolution in angular1 is not applicable for angular2. If anyone knows how to solve this please let me know. Thanks.

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Feb 3, 2016

What I describe above might also just be a bug, in which case please let me know will open a new issue.

user414 commented Feb 3, 2016

What I describe above might also just be a bug, in which case please let me know will open a new issue.

@zoechi

This comment has been minimized.

Show comment
Hide comment
@zoechi

zoechi Feb 4, 2016

Contributor

I think the server maintainers should be "motivated" to support HTML5 pushState by default. Google has already deprecated support for #!/https://googlewebmastercentral.blogspot.co.at/2015/10/deprecating-our-ajax-crawling-scheme.html.

Some good ideas how to point new users in the right direction would be great though. This is easily the most reported issue new users run into as soon as they add routing to their first example.

Contributor

zoechi commented Feb 4, 2016

I think the server maintainers should be "motivated" to support HTML5 pushState by default. Google has already deprecated support for #!/https://googlewebmastercentral.blogspot.co.at/2015/10/deprecating-our-ajax-crawling-scheme.html.

Some good ideas how to point new users in the right direction would be great though. This is easily the most reported issue new users run into as soon as they add routing to their first example.

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Feb 4, 2016

@zoechi The major argument in the comments above, is the functionality is an implementation-specific detail of the backend that the Angular2 app will run on. While I wholeheartedly agree when in comes to a production environment, I also think a tool should be provided that can handle the functionality in development.

In a development environment it would also be very useful to provide tooling that makes it easy to develop Angular2 front-end development without a backend.

I suggest extending lite-server with the functionality in question as that's the tool recommended in the Angular2 documentation.

I already added a PR to support the functionality in live-server here prior to the change to lite-server. If there is a enough demand for the feature I'll consider submitting a PR with the changes to the lite-server project.

Redirecting to index.html is easy. Detaching the path and reattaching it as a hash fragment is also not too difficult. The problem is, when Angular2 is set to use PathLocationStrategy it doesn't know to detect and route to the path attached as a hash fragment.

The code I provided above will work, I actually use it in production @ http://evanplaice.com because my site runs serverless from a S3 bucket. Unfortunately, it requires copying the following snippet into the constructor of the main application component.

export class MainComponent {
  constructor (@Inject(Location) location) {
    resolveHashURL(location);
  }
}

function resolveHashURL (location) {
  let hash = location.platformStrategy._platformLocation.hash;
  if (hash) {
    let path = hash.substring(1);
    // console.log('RedirectTo: ' + path);
    location.go(path);
  }
}

It would be ideal to implement this as a new location provider that could be used to override the PathLocationStrategy during development.

Either way, the broken redirect nature of PathLocationStrategy running without a sufficient backend will continue to baffle new users. Specifically, those who don't understand back-end development.

evanplaice commented Feb 4, 2016

@zoechi The major argument in the comments above, is the functionality is an implementation-specific detail of the backend that the Angular2 app will run on. While I wholeheartedly agree when in comes to a production environment, I also think a tool should be provided that can handle the functionality in development.

In a development environment it would also be very useful to provide tooling that makes it easy to develop Angular2 front-end development without a backend.

I suggest extending lite-server with the functionality in question as that's the tool recommended in the Angular2 documentation.

I already added a PR to support the functionality in live-server here prior to the change to lite-server. If there is a enough demand for the feature I'll consider submitting a PR with the changes to the lite-server project.

Redirecting to index.html is easy. Detaching the path and reattaching it as a hash fragment is also not too difficult. The problem is, when Angular2 is set to use PathLocationStrategy it doesn't know to detect and route to the path attached as a hash fragment.

The code I provided above will work, I actually use it in production @ http://evanplaice.com because my site runs serverless from a S3 bucket. Unfortunately, it requires copying the following snippet into the constructor of the main application component.

export class MainComponent {
  constructor (@Inject(Location) location) {
    resolveHashURL(location);
  }
}

function resolveHashURL (location) {
  let hash = location.platformStrategy._platformLocation.hash;
  if (hash) {
    let path = hash.substring(1);
    // console.log('RedirectTo: ' + path);
    location.go(path);
  }
}

It would be ideal to implement this as a new location provider that could be used to override the PathLocationStrategy during development.

Either way, the broken redirect nature of PathLocationStrategy running without a sufficient backend will continue to baffle new users. Specifically, those who don't understand back-end development.

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Feb 4, 2016

@evanplaice I apologize in advanced for my lack of back-end development knowledge. However, I can't seem to see what that solution provides that is not already working with a plain rewrite. At least for me the problem is not that there is issues when going to /foo/bar since this works fine with PathLocationStrategy and a plain rewrite to index.html in apache, my problem is, as described in the OP of angular1 issue, that once at /foo/bar a href="#foobar" cause a navigation event to /#foobar and doesn't just take you to the id foobar within that page. Again this might just be a bug and/or a configuration issue and have nothing to do with HashLocation vs PathLocation. Maybe I'm missing some configuration but that solution doesn't address that problem, or maybe it wasn't address as a solution to my problem...

user414 commented Feb 4, 2016

@evanplaice I apologize in advanced for my lack of back-end development knowledge. However, I can't seem to see what that solution provides that is not already working with a plain rewrite. At least for me the problem is not that there is issues when going to /foo/bar since this works fine with PathLocationStrategy and a plain rewrite to index.html in apache, my problem is, as described in the OP of angular1 issue, that once at /foo/bar a href="#foobar" cause a navigation event to /#foobar and doesn't just take you to the id foobar within that page. Again this might just be a bug and/or a configuration issue and have nothing to do with HashLocation vs PathLocation. Maybe I'm missing some configuration but that solution doesn't address that problem, or maybe it wasn't address as a solution to my problem...

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Feb 4, 2016

@user414 The problem occurs when somebody navigates to your side using an inbound link (ex bookmark). When the application loads the server will redirect to index.html and the rest of the url will be lost.

Instead, if the rewrite re-attaches the rest of the path as a hash fragment the application can follow the route when it bootstraps. Of course, that would break an inbound link that contains a hash anchor so there needs to be a way to escape it and escape it during load.

evanplaice commented Feb 4, 2016

@user414 The problem occurs when somebody navigates to your side using an inbound link (ex bookmark). When the application loads the server will redirect to index.html and the rest of the url will be lost.

Instead, if the rewrite re-attaches the rest of the path as a hash fragment the application can follow the route when it bootstraps. Of course, that would break an inbound link that contains a hash anchor so there needs to be a way to escape it and escape it during load.

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Feb 4, 2016

@evanplaice Actually that is a problem, but not as much a concern as a plain href in a plain page like below

<a href="#foobar">Problem</a>
<div id="foobar">
</div>

The above doesn't work currently using PathLocationStrategy.

user414 commented Feb 4, 2016

@evanplaice Actually that is a problem, but not as much a concern as a plain href in a plain page like below

<a href="#foobar">Problem</a>
<div id="foobar">
</div>

The above doesn't work currently using PathLocationStrategy.

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Feb 4, 2016

@user414 Oh... I didn't realize that. So escaping anchor links would be a non issue because they don't work anyway. I suppose anchor links probably fall under the DOM-specific category. I wonder if they're going to provide an onClick -> scrollTo feature as a replacement.

evanplaice commented Feb 4, 2016

@user414 Oh... I didn't realize that. So escaping anchor links would be a non issue because they don't work anyway. I suppose anchor links probably fall under the DOM-specific category. I wonder if they're going to provide an onClick -> scrollTo feature as a replacement.

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Feb 4, 2016

For people having the same problem I went back to old school way to make this work for now

<a class="pointer"(click)="navigateLocally('foobar')">

and the function just does

navigateLocally(anchorName: string) {
 document.location.hash = anchorName;
}

The pointer class just change the cursor back to href since there's no cursor without href. This fixes it since document.location has the right url of where you were, for example /user/15 but history.pushState() doesn't and lead you back to /#foobar instead of /user/15#foobar. Not so elegant, but will do the job for now. I agree with @evanplaice, something provided in the framework like scrollTo would be ideal.

user414 commented Feb 4, 2016

For people having the same problem I went back to old school way to make this work for now

<a class="pointer"(click)="navigateLocally('foobar')">

and the function just does

navigateLocally(anchorName: string) {
 document.location.hash = anchorName;
}

The pointer class just change the cursor back to href since there's no cursor without href. This fixes it since document.location has the right url of where you were, for example /user/15 but history.pushState() doesn't and lead you back to /#foobar instead of /user/15#foobar. Not so elegant, but will do the job for now. I agree with @evanplaice, something provided in the framework like scrollTo would be ideal.

@richmwatts

This comment has been minimized.

Show comment
Hide comment
@richmwatts

richmwatts Mar 28, 2016

This tripped me up for about an hour.

Please default to HashLocationStrategy and also be clear (remind) us in the documentation that when overriding the ROUTER_PROVIDERS config in the bootstrap phase to remove other calls to import and include ROUTER_PROVIDERS else where in the app!

[as long as the latter statement is correct]

richmwatts commented Mar 28, 2016

This tripped me up for about an hour.

Please default to HashLocationStrategy and also be clear (remind) us in the documentation that when overriding the ROUTER_PROVIDERS config in the bootstrap phase to remove other calls to import and include ROUTER_PROVIDERS else where in the app!

[as long as the latter statement is correct]

@CaptainCodeman

This comment has been minimized.

Show comment
Hide comment
@CaptainCodeman

CaptainCodeman Mar 28, 2016

Noooooooo - please keep it defaulted to the future, not the past.

This should be in documentation for the build / dev tools concerned. IMO, it's not really an Angular issue.

CaptainCodeman commented Mar 28, 2016

Noooooooo - please keep it defaulted to the future, not the past.

This should be in documentation for the build / dev tools concerned. IMO, it's not really an Angular issue.

@damiandennis

This comment has been minimized.

Show comment
Hide comment
@damiandennis

damiandennis Mar 28, 2016

yeah please keep push-state as the default...

damiandennis commented Mar 28, 2016

yeah please keep push-state as the default...

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Apr 1, 2016

I thought I would open a new issue for the anchor since it's not really related to this, looked for existing one in the issues and found one here

#5859

However, the funny thing is that the OP just had a mistake in their code and the provided solution worked. Well after investigation why mine didn't, I finally found out what was the issue about the anchor not working. It all started with the instructions published in the angular.io site here

https://angular.io/docs/ts/latest/guide/router.html#!#base-href

I never thought about it, assuming that doing

<base href="/">

was equivalent to

provide(APP_BASE_HREF, { useValue: '/' } )

However, it is not. Never thought about it but of course if you are using push state which is the default you will forward most of your url to /index.html and then the browser will see that base href is "/" in there and then any anchor tag like

<a href="#myid">Test</a>

will just go to /#myid and trigger navigation if you were at /order/... So obvious but completely overlooked it without thinking. Will open issue with angular.io so they can modify the documentation. There's a plunker below showing the issue.

http://plnkr.co/edit/3PIVAPEdK8S9hhtZfAsX?p=preview

On plnkr it doesn't work with the base href because of modification they do but just download the code and run it locally switching between base href in index and the provide you will see that any anchor will point to /#myid not whatever path you were on.

user414 commented Apr 1, 2016

I thought I would open a new issue for the anchor since it's not really related to this, looked for existing one in the issues and found one here

#5859

However, the funny thing is that the OP just had a mistake in their code and the provided solution worked. Well after investigation why mine didn't, I finally found out what was the issue about the anchor not working. It all started with the instructions published in the angular.io site here

https://angular.io/docs/ts/latest/guide/router.html#!#base-href

I never thought about it, assuming that doing

<base href="/">

was equivalent to

provide(APP_BASE_HREF, { useValue: '/' } )

However, it is not. Never thought about it but of course if you are using push state which is the default you will forward most of your url to /index.html and then the browser will see that base href is "/" in there and then any anchor tag like

<a href="#myid">Test</a>

will just go to /#myid and trigger navigation if you were at /order/... So obvious but completely overlooked it without thinking. Will open issue with angular.io so they can modify the documentation. There's a plunker below showing the issue.

http://plnkr.co/edit/3PIVAPEdK8S9hhtZfAsX?p=preview

On plnkr it doesn't work with the base href because of modification they do but just download the code and run it locally switching between base href in index and the provide you will see that any anchor will point to /#myid not whatever path you were on.

@guaido79

This comment has been minimized.

Show comment
Hide comment
@guaido79

guaido79 Apr 2, 2016

Please default to HashLocationStrategy.
With push you can navigate from / to /resource, fine, but not direcly /resource, so, no more bookmarkable address. If i'm wrong on bookmarkable please report. Thanks

guaido79 commented Apr 2, 2016

Please default to HashLocationStrategy.
With push you can navigate from / to /resource, fine, but not direcly /resource, so, no more bookmarkable address. If i'm wrong on bookmarkable please report. Thanks

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Apr 2, 2016

@guaido79 Must be a configuration issue, since now after this base href fix above everything works as intended, bookmarking included. For sure it would be nice if the documentation could use a sample config for nginx and apache.

user414 commented Apr 2, 2016

@guaido79 Must be a configuration issue, since now after this base href fix above everything works as intended, bookmarking included. For sure it would be nice if the documentation could use a sample config for nginx and apache.

@guaido79

This comment has been minimized.

Show comment
Hide comment
@guaido79

guaido79 Apr 2, 2016

@user414 thanks, but i think that the default routing configuration should work out of the box without the need of any server configuration, what do you think?

guaido79 commented Apr 2, 2016

@user414 thanks, but i think that the default routing configuration should work out of the box without the need of any server configuration, what do you think?

@user414

This comment has been minimized.

Show comment
Hide comment
@user414

user414 Apr 2, 2016

@guaido79 I don't think that's possible, since for single page app and push state you implicitly are also talking about a server backend. There's just no way around it, or at least none that I know of:-) Maybe one day they will come up with some convention, of maybe a html meta tag which could tell nginx to use a default template to redirect all non existing file path to /index.html but we are not there yet. For now you have to set it up yourself.

user414 commented Apr 2, 2016

@guaido79 I don't think that's possible, since for single page app and push state you implicitly are also talking about a server backend. There's just no way around it, or at least none that I know of:-) Maybe one day they will come up with some convention, of maybe a html meta tag which could tell nginx to use a default template to redirect all non existing file path to /index.html but we are not there yet. For now you have to set it up yourself.

@guaido79

This comment has been minimized.

Show comment
Hide comment
@guaido79

guaido79 Apr 2, 2016

That's why I think HashLocationStrategy should be the default, no configuration for server (ex. Apache) is needed.

guaido79 commented Apr 2, 2016

That's why I think HashLocationStrategy should be the default, no configuration for server (ex. Apache) is needed.

@wardbell

This comment has been minimized.

Show comment
Hide comment
@wardbell

wardbell Apr 2, 2016

Contributor

The default will remain push state.

It is too hard to move from hash to push state strategy later if that's what you ultimately want whereas a decision to go the other way later is comparatively trivial.

Sorry but this ship has sailed. You can easily switch to hash for your project.

Contributor

wardbell commented Apr 2, 2016

The default will remain push state.

It is too hard to move from hash to push state strategy later if that's what you ultimately want whereas a decision to go the other way later is comparatively trivial.

Sorry but this ship has sailed. You can easily switch to hash for your project.

@cecotw

This comment has been minimized.

Show comment
Hide comment
@cecotw

cecotw Apr 11, 2016

What if you don't want a SPA or only want it on a specific server side route? For example, I have a mostly traditional server side based .NET app with an ExampleController.cs. From there, I only want to serve up an angular app on one particular action(page). Lets call it AngularExample action.
So I hit > http://localhost/myproject/example/angularexample
That page has my base href set to: <base href="/myproject"/>so that it loads all my template paths correctly.
Even with HashLocationStrategy enabled, Angular is kicking my URL to http://localhost/myproject/#/someangularroute and dropped my controller/action routing.

Now, on page refresh/bookmark, the site just hits the default route set up in my server side routing because angular dropped my controller/action part of the path. Setting the APP_BASE_HREF just appends the prefix after the hash. How do I communicate to angular about my server side routing? Shouldn't the HashLocationStrategy append the angular route to the current location instead of cutting it down to the base href value?

cecotw commented Apr 11, 2016

What if you don't want a SPA or only want it on a specific server side route? For example, I have a mostly traditional server side based .NET app with an ExampleController.cs. From there, I only want to serve up an angular app on one particular action(page). Lets call it AngularExample action.
So I hit > http://localhost/myproject/example/angularexample
That page has my base href set to: <base href="/myproject"/>so that it loads all my template paths correctly.
Even with HashLocationStrategy enabled, Angular is kicking my URL to http://localhost/myproject/#/someangularroute and dropped my controller/action routing.

Now, on page refresh/bookmark, the site just hits the default route set up in my server side routing because angular dropped my controller/action part of the path. Setting the APP_BASE_HREF just appends the prefix after the hash. How do I communicate to angular about my server side routing? Shouldn't the HashLocationStrategy append the angular route to the current location instead of cutting it down to the base href value?

@CaptainCodeman

This comment has been minimized.

Show comment
Hide comment
@CaptainCodeman

CaptainCodeman Apr 11, 2016

You're possibly going to have other issues as the type of dynamically created template you hint at relying on isn't going to be supported in future from what I hear / have read.

The whole approach of adding angular to a page to augment existing content (or loading content into a page and compiling it) to add behavior through directives will no longer be valid once the compiler is removed from the runtime and moved to a build step (unless there is an option to keep it).

CaptainCodeman commented Apr 11, 2016

You're possibly going to have other issues as the type of dynamically created template you hint at relying on isn't going to be supported in future from what I hear / have read.

The whole approach of adding angular to a page to augment existing content (or loading content into a page and compiling it) to add behavior through directives will no longer be valid once the compiler is removed from the runtime and moved to a build step (unless there is an option to keep it).

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice Apr 21, 2016

@CaptainCodeman I hope that's not the case, using Angular to embed content in an existing page is an extremely useful use case.

As of now, I wouldn't use ng2 in production without a compilation step first due to performance reasons but that wouldn't necessarily be the case once ES6 is fully supported.

evanplaice commented Apr 21, 2016

@CaptainCodeman I hope that's not the case, using Angular to embed content in an existing page is an extremely useful use case.

As of now, I wouldn't use ng2 in production without a compilation step first due to performance reasons but that wouldn't necessarily be the case once ES6 is fully supported.

@wardbell

This comment has been minimized.

Show comment
Hide comment
@wardbell

wardbell Apr 23, 2016

Contributor

My understanding is that we will have the choice of keeping jit compilation on the client at the cost of bigger payload. Server side compilation will always be optional, never a requirement.

Again, that is my understanding. Therefore I do not think this statement is or will be true:

The whole approach of adding angular to a page to augment existing content (or loading content into a page and compiling it) to add behavior through directives will no longer be valid once the compiler is removed from the runtime and moved to a build step (unless there is an option to keep it).

Contributor

wardbell commented Apr 23, 2016

My understanding is that we will have the choice of keeping jit compilation on the client at the cost of bigger payload. Server side compilation will always be optional, never a requirement.

Again, that is my understanding. Therefore I do not think this statement is or will be true:

The whole approach of adding angular to a page to augment existing content (or loading content into a page and compiling it) to add behavior through directives will no longer be valid once the compiler is removed from the runtime and moved to a build step (unless there is an option to keep it).

@CaptainCodeman

This comment has been minimized.

Show comment
Hide comment
@CaptainCodeman

CaptainCodeman Apr 23, 2016

Just to clarify, there are two separate things at play here. Loading component templates at runtime and loading Html content.

It's been hinted that using templateUrl to load the template for a component will remain an option but there is no definitive statement that I can find that guarantees that it will be the case and the framework seems to be going in a direction away from it.

That's different to wanting to load content at runtime that may contain HTML elements, classes and attributes that you want components and/or directives to hook into and process so they become part of the Angular 2 page. Examples would be lazy-loading images in a product description snippet or adding some card popup to links.

AFAIK there is no $compile equivalent and no easy way for innerHtml content added to the page to become a live part of the app without a lot of work (part of why "include" is no longer an option). It was possible in Angular 1 but it doesn't appear to be possible or as easy in Angular 2.

Instead, it would be a case of loading the content and having the component responsible for it query for certain elements, classes and so on to add and manage the required behavior. Basically, going back to a jQuery-like approach OR wrapping something else that can handle the content and pick up elements in dynamically added content (e.g. polymer).

None of this is relevant to the router default being a hash or path, but it's likely of great significance to some people if it is the case.

CaptainCodeman commented Apr 23, 2016

Just to clarify, there are two separate things at play here. Loading component templates at runtime and loading Html content.

It's been hinted that using templateUrl to load the template for a component will remain an option but there is no definitive statement that I can find that guarantees that it will be the case and the framework seems to be going in a direction away from it.

That's different to wanting to load content at runtime that may contain HTML elements, classes and attributes that you want components and/or directives to hook into and process so they become part of the Angular 2 page. Examples would be lazy-loading images in a product description snippet or adding some card popup to links.

AFAIK there is no $compile equivalent and no easy way for innerHtml content added to the page to become a live part of the app without a lot of work (part of why "include" is no longer an option). It was possible in Angular 1 but it doesn't appear to be possible or as easy in Angular 2.

Instead, it would be a case of loading the content and having the component responsible for it query for certain elements, classes and so on to add and manage the required behavior. Basically, going back to a jQuery-like approach OR wrapping something else that can handle the content and pick up elements in dynamically added content (e.g. polymer).

None of this is relevant to the router default being a hash or path, but it's likely of great significance to some people if it is the case.

@IgorMinar

This comment has been minimized.

Show comment
Hide comment
@IgorMinar

IgorMinar Apr 26, 2016

Member

@CaptainCodeman - @wardbell is right. you can do online compilation but you'll pay for it via larger payload size.

we don't want router to default to hash location because that's an anti-pattern. it was useful in the old days before pushState was available, but those days are long gone. Yes, PathLocationStrategy requires server configuration but that's a very small cost for all the possibilities it unlocks.

At the end of the day, what matters the most is that we build apps that people like to use and we won't be able to achieve that if we need to make lots of requests to the server just to get started.

Member

IgorMinar commented Apr 26, 2016

@CaptainCodeman - @wardbell is right. you can do online compilation but you'll pay for it via larger payload size.

we don't want router to default to hash location because that's an anti-pattern. it was useful in the old days before pushState was available, but those days are long gone. Yes, PathLocationStrategy requires server configuration but that's a very small cost for all the possibilities it unlocks.

At the end of the day, what matters the most is that we build apps that people like to use and we won't be able to achieve that if we need to make lots of requests to the server just to get started.

@IgorMinar IgorMinar closed this Apr 26, 2016

@CaptainCodeman

This comment has been minimized.

Show comment
Hide comment
@CaptainCodeman

CaptainCodeman Apr 26, 2016

@IgorMinar It feels like you are replying to a comment I haven't actually made. At no time have I said that I think hash location should be the default or that there aren't trade-offs to having online vs offline compilation (which are choices for the app author to make).

@wardbell said my statement about dynamically loading content was incorrect. I was hoping that clarifying the related but distinct issues would enable them to be answered more easily.

Unless I hear otherwise I'll just assume that compiling content and wiring it up, something that was possible in Angular 1, won't actually be possible in Angular 2 and therefore my statement was correct.

CaptainCodeman commented Apr 26, 2016

@IgorMinar It feels like you are replying to a comment I haven't actually made. At no time have I said that I think hash location should be the default or that there aren't trade-offs to having online vs offline compilation (which are choices for the app author to make).

@wardbell said my statement about dynamically loading content was incorrect. I was hoping that clarifying the related but distinct issues would enable them to be answered more easily.

Unless I hear otherwise I'll just assume that compiling content and wiring it up, something that was possible in Angular 1, won't actually be possible in Angular 2 and therefore my statement was correct.

@evanplaice

This comment has been minimized.

Show comment
Hide comment
@evanplaice

evanplaice May 4, 2016

FYI, live-server now supports auto-redirecting for development via the '--spa' flag. This way inbound links using PathLocationStrategy will still work.

This will allow devs who lack back-end knowledge to get started on prototyping a front-end sans a specialized back-end support.

evanplaice commented May 4, 2016

FYI, live-server now supports auto-redirecting for development via the '--spa' flag. This way inbound links using PathLocationStrategy will still work.

This will allow devs who lack back-end knowledge to get started on prototyping a front-end sans a specialized back-end support.

@canaan5

This comment has been minimized.

Show comment
Hide comment
@canaan5

canaan5 Jun 20, 2017

Solution provided by evanplaice works in replacing the url but doesn't change the page, if you want to really navigate you have to you the navigation method from angular router.
constructor(private router: Router) { this.router.navigateByUrl(path); }

canaan5 commented Jun 20, 2017

Solution provided by evanplaice works in replacing the url but doesn't change the page, if you want to really navigate you have to you the navigation method from angular router.
constructor(private router: Router) { this.router.navigateByUrl(path); }

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment