@@ -303,9 +303,7 @@ function LocationHashbangInHtml5Url(appBase, hashPrefix) {
303
303
}
304
304
305
305
306
- LocationHashbangInHtml5Url . prototype =
307
- LocationHashbangUrl . prototype =
308
- LocationHtml5Url . prototype = {
306
+ var locationPrototype = {
309
307
310
308
/**
311
309
* Are we in html5 mode?
@@ -314,7 +312,7 @@ LocationHashbangInHtml5Url.prototype =
314
312
$$html5 : false ,
315
313
316
314
/**
317
- * Has any change been replacing ?
315
+ * Has any change been replacing?
318
316
* @private
319
317
*/
320
318
$$replace : false ,
@@ -530,6 +528,46 @@ LocationHashbangInHtml5Url.prototype =
530
528
}
531
529
} ;
532
530
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
+
533
571
function locationGetter ( property ) {
534
572
return function ( ) {
535
573
return this [ property ] ;
@@ -649,9 +687,14 @@ function $LocationProvider(){
649
687
* details about event object. Upon successful change
650
688
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
651
689
*
690
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
691
+ * the browser supports the HTML5 History API.
692
+ *
652
693
* @param {Object } angularEvent Synthetic event object.
653
694
* @param {string } newUrl New URL
654
695
* @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.
655
698
*/
656
699
657
700
/**
@@ -661,9 +704,14 @@ function $LocationProvider(){
661
704
* @description
662
705
* Broadcasted after a URL was changed.
663
706
*
707
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
708
+ * the browser supports the HTML5 History API.
709
+ *
664
710
* @param {Object } angularEvent Synthetic event object.
665
711
* @param {string } newUrl New URL
666
712
* @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.
667
715
*/
668
716
669
717
this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' ,
@@ -688,8 +736,29 @@ function $LocationProvider(){
688
736
$location = new LocationMode ( appBase , '#' + hashPrefix ) ;
689
737
$location . $$parseLinkUrl ( initialUrl , initialUrl ) ;
690
738
739
+ $location . $$state = $browser . state ( ) ;
740
+
691
741
var IGNORE_URI_REGEXP = / ^ \s * ( j a v a s c r i p t | m a i l t o ) : / i;
692
742
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
+
693
762
$rootElement . on ( 'click' , function ( event ) {
694
763
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
695
764
// currently we open nice url link and redirect then
@@ -740,52 +809,63 @@ function $LocationProvider(){
740
809
$browser . url ( $location . absUrl ( ) , true ) ;
741
810
}
742
811
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 ;
748
813
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 ( ) ;
760
833
} ) ;
761
834
762
835
// update browser
763
- var changeCounter = 0 ;
764
836
$rootScope . $watch ( function $locationWatch ( ) {
765
837
var oldUrl = $browser . url ( ) ;
838
+ var oldState = $browser . state ( ) ;
766
839
var currentReplace = $location . $$replace ;
767
840
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
+
770
845
$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 ) {
773
848
$location . $$parse ( oldUrl ) ;
849
+ $location . $$state = oldState ;
774
850
} 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 ) ;
777
854
}
778
855
} ) ;
779
856
}
857
+
780
858
$location . $$replace = false ;
781
859
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
783
862
} ) ;
784
863
785
864
return $location ;
786
865
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 ) ;
789
869
}
790
870
} ] ;
791
871
}
0 commit comments