@@ -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,47 @@ 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+ this . $$stateSent = false ; // has the state change been sent to $browser?
566+
567+ return this ;
568+ } ;
569+ } ) ;
570+
571+
533572function locationGetter ( property ) {
534573 return function ( ) {
535574 return this [ property ] ;
@@ -649,9 +688,14 @@ function $LocationProvider(){
649688 * details about event object. Upon successful change
650689 * {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
651690 *
691+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
692+ * the browser supports the HTML5 History API.
693+ *
652694 * @param {Object } angularEvent Synthetic event object.
653695 * @param {string } newUrl New URL
654696 * @param {string= } oldUrl URL that was before it was changed.
697+ * @param {string= } newState New history state object
698+ * @param {string= } oldState History state object that was before it was changed.
655699 */
656700
657701 /**
@@ -661,9 +705,14 @@ function $LocationProvider(){
661705 * @description
662706 * Broadcasted after a URL was changed.
663707 *
708+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
709+ * the browser supports the HTML5 History API.
710+ *
664711 * @param {Object } angularEvent Synthetic event object.
665712 * @param {string } newUrl New URL
666713 * @param {string= } oldUrl URL that was before it was changed.
714+ * @param {string= } newState New history state object
715+ * @param {string= } oldState History state object that was before it was changed.
667716 */
668717
669718 this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' ,
@@ -688,8 +737,30 @@ function $LocationProvider(){
688737 $location = new LocationMode ( appBase , '#' + hashPrefix ) ;
689738 $location . $$parseLinkUrl ( initialUrl , initialUrl ) ;
690739
740+ $location . $$state = $browser . state ( ) ;
741+ $location . $$stateSent = true ; // we're already in sync with $browser
742+
691743 var IGNORE_URI_REGEXP = / ^ \s * ( j a v a s c r i p t | m a i l t o ) : / i;
692744
745+ function setBrowserUrlWithFallback ( url , replace , state ) {
746+ var oldUrl = $location . url ( ) ;
747+ var oldState = $location . $$state ;
748+ try {
749+ $browser . url ( url , replace , state ) ;
750+
751+ // Make sure $location.state() returns referentially identical (not just deeply equal)
752+ // state object; this makes possible quick checking if the state changed in the digest
753+ // loop. Checking deep equality would be too expensive.
754+ $location . $$state = $browser . state ( ) ;
755+ } catch ( e ) {
756+ // Restore old values if pushState fails
757+ $location . url ( oldUrl ) ;
758+ $location . $$state = oldState ;
759+
760+ throw e ;
761+ }
762+ }
763+
693764 $rootElement . on ( 'click' , function ( event ) {
694765 // TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
695766 // currently we open nice url link and redirect then
@@ -741,51 +812,59 @@ function $LocationProvider(){
741812 }
742813
743814 // 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 ( ) ;
748-
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- }
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+ afterLocationChange ( oldUrl , oldState ) ;
829+ }
830+ } ) ;
831+ if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
760832 } ) ;
761833
762834 // update browser
763835 var changeCounter = 0 ;
764836 $rootScope . $watch ( function $locationWatch ( ) {
765837 var oldUrl = $browser . url ( ) ;
838+ var oldState = $browser . state ( ) ;
766839 var currentReplace = $location . $$replace ;
840+ var currentStateSent = $location . $$stateSent && changeCounter ;
767841
768- if ( ! changeCounter || oldUrl != $location . absUrl ( ) ) {
842+ if ( ! changeCounter || oldUrl !== $location . absUrl ( ) ||
843+ ( $location . $$html5 && $sniffer . history && oldState !== $location . $$state ) ) {
769844 changeCounter ++ ;
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+ currentStateSent ? null : $location . $$state ) ;
853+ afterLocationChange ( oldUrl , oldState ) ;
777854 }
778855 } ) ;
779856 }
857+ $location . $$stateSent = true ;
780858 $location . $$replace = false ;
781859
782860 return changeCounter ;
783861 } ) ;
784862
785863 return $location ;
786864
787- function afterLocationChange ( oldUrl ) {
788- $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ) ;
865+ function afterLocationChange ( oldUrl , oldState ) {
866+ $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ,
867+ $location . $$state , oldState ) ;
789868 }
790869} ] ;
791870}
0 commit comments