@@ -303,9 +303,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
303303}
304304
305305
306- LocationHashbangInHtml5Url . prototype =
307- LocationHashbangUrl . prototype =
308- LocationHtml5Url . prototype = {
306+ var locationPrototype = {
309307
310308 /**
311309 * Are we in html5 mode?
@@ -314,7 +312,7 @@ LocationHashbangInHtml5Url.prototype =
314312 $$html5 : false ,
315313
316314 /**
317- * Has any change been replacing ?
315+ * Has any change been replacing?
318316 * @private
319317 */
320318 $$replace : false ,
@@ -530,6 +528,46 @@ LocationHashbangInHtml5Url.prototype =
530528 }
531529} ;
532530
531+ forEach ( [ LocationHashbangInHtml5Url , LocationHashbangUrl , LocationHtml5Url ] , function ( Location ) {
532+ Location . prototype = Object . create ( locationPrototype ) ;
533+
534+ /**
535+ * @ngdoc method
536+ * @name $location#state
537+ *
538+ * @description
539+ * This method is getter / setter.
540+ *
541+ * Return the history state object when called without any parameter.
542+ *
543+ * Change the history state object when called with one parameter and return `$location`.
544+ * The state object is later passed to `pushState` or `replaceState`.
545+ *
546+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
547+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
548+ * older browsers (like IE9 or Android < 4.0), don't use this method.
549+ *
550+ * @param {object= } state State object for pushState or replaceState
551+ * @return {object } state
552+ */
553+ Location . prototype . state = function ( state ) {
554+ if ( ! arguments . length )
555+ return this . $$state ;
556+
557+ if ( Location !== LocationHtml5Url || ! this . $$html5 ) {
558+ throw $locationMinErr ( 'nostate' , 'History API state support is available only ' +
559+ 'in HTML5 mode and only in browsers supporting HTML5 History API' ) ;
560+ }
561+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
562+ // but we're changing the $$state reference to $browser.state() during the $digest
563+ // so the modification window is narrow.
564+ this . $$state = isUndefined ( state ) ? null : state ;
565+
566+ return this ;
567+ } ;
568+ } ) ;
569+
570+
533571function locationGetter ( property ) {
534572 return function ( ) {
535573 return this [ property ] ;
@@ -649,9 +687,14 @@ function $LocationProvider(){
649687 * details about event object. Upon successful change
650688 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
651689 *
690+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
691+ * the browser supports the HTML5 History API.
692+ *
652693 * @param {Object } angularEvent Synthetic event object.
653694 * @param {string } newUrl New URL
654695 * @param {string= } oldUrl URL that was before it was changed.
696+ * @param {string= } newState New history state object
697+ * @param {string= } oldState History state object that was before it was changed.
655698 */
656699
657700 /**
@@ -661,9 +704,14 @@ function $LocationProvider(){
661704 * @description
662705 * Broadcasted after a URL was changed.
663706 *
707+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
708+ * the browser supports the HTML5 History API.
709+ *
664710 * @param {Object } angularEvent Synthetic event object.
665711 * @param {string } newUrl New URL
666712 * @param {string= } oldUrl URL that was before it was changed.
713+ * @param {string= } newState New history state object
714+ * @param {string= } oldState History state object that was before it was changed.
667715 */
668716
669717 this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' ,
@@ -688,8 +736,29 @@ function $LocationProvider(){
688736 $location = new LocationMode ( appBase , '#' + hashPrefix ) ;
689737 $location . $$parseLinkUrl ( initialUrl , initialUrl ) ;
690738
739+ $location . $$state = $browser . state ( ) ;
740+
691741 var IGNORE_URI_REGEXP = / ^ \s * ( j a v a s c r i p t | m a i l t o ) : / i;
692742
743+ function setBrowserUrlWithFallback ( url , replace , state ) {
744+ var oldUrl = $location . url ( ) ;
745+ var oldState = $location . $$state ;
746+ try {
747+ $browser . url ( url , replace , state ) ;
748+
749+ // Make sure $location.state() returns referentially identical (not just deeply equal)
750+ // state object; this makes possible quick checking if the state changed in the digest
751+ // loop. Checking deep equality would be too expensive.
752+ $location . $$state = $browser . state ( ) ;
753+ } catch ( e ) {
754+ // Restore old values if pushState fails
755+ $location . url ( oldUrl ) ;
756+ $location . $$state = oldState ;
757+
758+ throw e ;
759+ }
760+ }
761+
693762 $rootElement . on ( 'click' , function ( event ) {
694763 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
695764 // currently we open nice url link and redirect then
@@ -740,52 +809,63 @@ function $LocationProvider(){
740809 $browser . url ( $location . absUrl ( ) , true ) ;
741810 }
742811
743- // update $location when $browser url changes
744- $browser . onUrlChange ( function ( newUrl ) {
745- if ( $location . absUrl ( ) != newUrl ) {
746- $rootScope . $evalAsync ( function ( ) {
747- var oldUrl = $location . absUrl ( ) ;
812+ var initializing = true ;
748813
749- $location . $$parse ( newUrl ) ;
750- if ( $rootScope . $broadcast ( '$locationChangeStart' , newUrl ,
751- oldUrl ) . defaultPrevented ) {
752- $location . $$parse ( oldUrl ) ;
753- $browser . url ( oldUrl ) ;
754- } else {
755- afterLocationChange ( oldUrl ) ;
756- }
757- } ) ;
758- if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
759- }
814+ // update $location when $browser url changes
815+ $browser . onUrlChange ( function ( newUrl , newState ) {
816+ $rootScope . $evalAsync ( function ( ) {
817+ var oldUrl = $location . absUrl ( ) ;
818+ var oldState = $location . $$state ;
819+
820+ $location . $$parse ( newUrl ) ;
821+ $location . $$state = newState ;
822+ if ( $rootScope . $broadcast ( '$locationChangeStart' , newUrl , oldUrl ,
823+ newState , oldState ) . defaultPrevented ) {
824+ $location . $$parse ( oldUrl ) ;
825+ $location . $$state = oldState ;
826+ setBrowserUrlWithFallback ( oldUrl , false , oldState ) ;
827+ } else {
828+ initializing = false ;
829+ afterLocationChange ( oldUrl , oldState ) ;
830+ }
831+ } ) ;
832+ if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
760833 } ) ;
761834
762835 // update browser
763- var changeCounter = 0 ;
764836 $rootScope . $watch ( function $locationWatch ( ) {
765837 var oldUrl = $browser . url ( ) ;
838+ var oldState = $browser . state ( ) ;
766839 var currentReplace = $location . $$replace ;
767840
768- if ( ! changeCounter || oldUrl != $location . absUrl ( ) ) {
769- changeCounter ++ ;
841+ if ( initializing || oldUrl !== $location . absUrl ( ) ||
842+ ( $location . $$html5 && $sniffer . history && oldState !== $location . $$state ) ) {
843+ initializing = false ;
844+
770845 $rootScope . $evalAsync ( function ( ) {
771- if ( $rootScope . $broadcast ( '$locationChangeStart' , $location . absUrl ( ) , oldUrl ) .
772- defaultPrevented ) {
846+ if ( $rootScope . $broadcast ( '$locationChangeStart' , $location . absUrl ( ) , oldUrl ,
847+ $location . $$state , oldState ) . defaultPrevented ) {
773848 $location . $$parse ( oldUrl ) ;
849+ $location . $$state = oldState ;
774850 } else {
775- $browser . url ( $location . absUrl ( ) , currentReplace ) ;
776- afterLocationChange ( oldUrl ) ;
851+ setBrowserUrlWithFallback ( $location . absUrl ( ) , currentReplace ,
852+ oldState === $location . $$state ? null : $location . $$state ) ;
853+ afterLocationChange ( oldUrl , oldState ) ;
777854 }
778855 } ) ;
779856 }
857+
780858 $location . $$replace = false ;
781859
782- return changeCounter ;
860+ // we don't need to return anything because $evalAsync will make the digest loop dirty when
861+ // there is a change
783862 } ) ;
784863
785864 return $location ;
786865
787- function afterLocationChange ( oldUrl ) {
788- $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ) ;
866+ function afterLocationChange ( oldUrl , oldState ) {
867+ $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ,
868+ $location . $$state , oldState ) ;
789869 }
790870} ] ;
791871}
0 commit comments