-
Notifications
You must be signed in to change notification settings - Fork 27.4k
feat($location): add support for History API state handling #9027
Conversation
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
The |
@IgorMinar I'm still not sure what to think about the You'd think the |
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
// Android Browser BFCache causes location, history reference to become stale. | ||
if (location !== window.location) location = window.location; | ||
if (history !== window.history) history = window.history; | ||
|
||
// setter | ||
if (url) { | ||
if (lastBrowserUrl == url) return; | ||
if (lastBrowserUrl == url && (!$sniffer.history || equals(lastBrowserState, state))) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@IgorMinar IIRC this if
was originally meant to prevent infinite digest in IE9 in hash mode. I added comparing states, I could also check title
(though browsers don't expose the title
param used in pushState
in any way) but I'm not sure if it's needed in HTML5 mode?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no need to use equals
here. we can just compare references since browsers always create new instance of history.state
when state change occurs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you are right. this stuff should not be needed at all. let's remove it until we see a need for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
OK, I'm leaving it as it was then as it's for the hash mode only anyway.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm, actually, I need to be able to do $location.pushState(state, '', 'the-same-url')
so I need to have a relaxed condition (I've already written a test for that). Ideally I'd just skip the URL check here in HTML5 mode but $browser
doesn't know about such a concept. I'll think about it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For reference, this logic was added in ffb2701; am I right that it affects only the hash-fallback-in-html5-mode case?
Anyway, while it was added because of oldIE, people may depend on the fact they can call $location.url('the-same-url')
and expect nothing to happen in such a case. I might skip this logic if $sniffer.history
but then the behavior would depend on whether the browser supports the History API or not which is not ideal.
Ideally I'd skip this logic only if the change originated via $location.pushState/replaceState
, but I can't detect that from within $browser
, mainly because history.pushState(undefined, '', 'new-url')
is still valid so the basic state === undefined
check won't be enough.
This hash-in-html5-mode fallback is a real PITA. :)
02dc2aa
to
fd2d6c0
Compare
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
@IgorMinar any remarks? |
Adds $location pushState & replaceState methods acting as proxies to history pushState & replaceState methods. This allows using pushState to change state and title as well as URL. Note that these methods are not compatible with browsers not supporting the HTML5 History API, e.g. IE 9. Closes angular#9027
else { | ||
history.pushState(null, '', url); | ||
// Crazy Opera Bug: http://my.opera.com/community/forums/topic.dml?id=1185462 | ||
baseElement.attr('href', baseElement.attr('href')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just merged #8589 so this hunk is now obsolete
Note: current design assumes that If both are needed to be changed, one has to invoke |
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
One note: currently one invocation of |
|
||
$location.$$parse(newUrl); | ||
if ($rootScope.$broadcast('$locationChangeStart', newUrl, | ||
oldUrl).defaultPrevented) { | ||
$location.$$state = copy(newState); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why copy?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Because (at least in Chrome) history.state cannot be swapped but can be augumented:
history.pushState({a: 2}, 'a', 'b');
history.state; // {a: 2}
history.state.b = 3;
history.state; // {a: 2, b: 3};
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more important copy
is in Location#state
, though as then we save the state object passed by the user and nothing prevents them from modifying it afterwards (i'd guess it may be common).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we should add this reasoning into the code as a comment so that we don't forget.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
ok, will do
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
var currentReplace = $location.$$replace; | ||
var currentStateSent = $location.$$stateSent && changeCounter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what's the deal with changeCounter
but without this check here I was getting into the if
below with equal URLs & states and $$stateSent === true
which was causing the state to be set to null
(& breaking a test at that).
@IgorMinar ready for the final review. |
One weird result is that if we pushed |
if (lastBrowserUrl == url) return; | ||
// Prevent IE<10 from getting into redirect loop when in LocationHashbangInHtml5Url mode. | ||
// See https://github.com/angular/angular.js/commit/ffb2701 | ||
if (lastBrowserUrl === url && (!$sniffer.history || history.state === state)) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if this fix is just for ie<10 as you documented then we'll never need to check history.state
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
updated a comment
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
@IgorMinar @tbosch I merged Igor's changes and rebased over master and now I have one test failure on a test introduced today: angular.js/test/ng/browserSpecs.js Line 820 in 8ee1ba4
Could you have a look? |
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
Adds $location state method allowing to get/set a History API state via pushState & replaceState methods. Note that: - Angular treats states undefined and null as the same; trying to change one to the other without touching the URL won't do anything. This is necessary to prevent infinite digest loops when setting the URL to itself in IE<10 in the HTML5 hash fallback mode. - The state() method is not compatible with browsers not supporting the HTML5 History API, e.g. IE 9 or Android < 4.0. Closes angular#9027
This is a continuation of #3325, re-created per my e-mail thread with @IgorMinar.
EDIT: new commit message:
Adds $location state method allowing to get/set a History API state via
pushState & replaceState methods.
Note that:
one to the other without touching the URL won't do anything. This is necessary
to prevent infinite digest loops when setting the URL to itself in IE<10 in
the HTML5 hash fallback mode.
the HTML5 History API, e.g. IE 9 or Android < 4.0.