@@ -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 ,
@@ -531,6 +529,46 @@ LocationHashbangInHtml5Url.prototype =
531
529
}
532
530
} ;
533
531
532
+ forEach ( [ LocationHashbangInHtml5Url , LocationHashbangUrl , LocationHtml5Url ] , function ( Location ) {
533
+ Location . prototype = Object . create ( locationPrototype ) ;
534
+
535
+ /**
536
+ * @ngdoc method
537
+ * @name $location#state
538
+ *
539
+ * @description
540
+ * This method is getter / setter.
541
+ *
542
+ * Return the history state object when called without any parameter.
543
+ *
544
+ * Change the history state object when called with one parameter and return `$location`.
545
+ * The state object is later passed to `pushState` or `replaceState`.
546
+ *
547
+ * NOTE: This method is supported only in HTML5 mode and only in browsers supporting
548
+ * the HTML5 History API (i.e. methods `pushState` and `replaceState`). If you need to support
549
+ * older browsers (like IE9 or Android < 4.0), don't use this method.
550
+ *
551
+ * @param {object= } state State object for pushState or replaceState
552
+ * @return {object } state
553
+ */
554
+ Location . prototype . state = function ( state ) {
555
+ if ( ! arguments . length )
556
+ return this . $$state ;
557
+
558
+ if ( Location !== LocationHtml5Url || ! this . $$html5 ) {
559
+ throw $locationMinErr ( 'nostate' , 'History API state support is available only ' +
560
+ 'in HTML5 mode and only in browsers supporting HTML5 History API' ) ;
561
+ }
562
+ // The user might modify `stateObject` after invoking `$location.state(stateObject)`
563
+ // but we're changing the $$state reference to $browser.state() during the $digest
564
+ // so the modification window is narrow.
565
+ this . $$state = isUndefined ( state ) ? null : state ;
566
+
567
+ return this ;
568
+ } ;
569
+ } ) ;
570
+
571
+
534
572
function locationGetter ( property ) {
535
573
return function ( ) {
536
574
return this [ property ] ;
@@ -650,9 +688,14 @@ function $LocationProvider(){
650
688
* details about event object. Upon successful change
651
689
* {@link ng.$location#events_$locationChangeSuccess $locationChangeSuccess} is fired.
652
690
*
691
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
692
+ * the browser supports the HTML5 History API.
693
+ *
653
694
* @param {Object } angularEvent Synthetic event object.
654
695
* @param {string } newUrl New URL
655
696
* @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.
656
699
*/
657
700
658
701
/**
@@ -662,9 +705,14 @@ function $LocationProvider(){
662
705
* @description
663
706
* Broadcasted after a URL was changed.
664
707
*
708
+ * The `newState` and `oldState` parameters may be defined only in HTML5 mode and when
709
+ * the browser supports the HTML5 History API.
710
+ *
665
711
* @param {Object } angularEvent Synthetic event object.
666
712
* @param {string } newUrl New URL
667
713
* @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.
668
716
*/
669
717
670
718
this . $get = [ '$rootScope' , '$browser' , '$sniffer' , '$rootElement' ,
@@ -689,8 +737,29 @@ function $LocationProvider(){
689
737
$location = new LocationMode ( appBase , '#' + hashPrefix ) ;
690
738
$location . $$parseLinkUrl ( initialUrl , initialUrl ) ;
691
739
740
+ $location . $$state = $browser . state ( ) ;
741
+
692
742
var IGNORE_URI_REGEXP = / ^ \s * ( j a v a s c r i p t | m a i l t o ) : / i;
693
743
744
+ function setBrowserUrlWithFallback ( url , replace , state ) {
745
+ var oldUrl = $location . url ( ) ;
746
+ var oldState = $location . $$state ;
747
+ try {
748
+ $browser . url ( url , replace , state ) ;
749
+
750
+ // Make sure $location.state() returns referentially identical (not just deeply equal)
751
+ // state object; this makes possible quick checking if the state changed in the digest
752
+ // loop. Checking deep equality would be too expensive.
753
+ $location . $$state = $browser . state ( ) ;
754
+ } catch ( e ) {
755
+ // Restore old values if pushState fails
756
+ $location . url ( oldUrl ) ;
757
+ $location . $$state = oldState ;
758
+
759
+ throw e ;
760
+ }
761
+ }
762
+
694
763
$rootElement . on ( 'click' , function ( event ) {
695
764
// TODO(vojta): rewrite link when opening in new tab/window (in legacy browser)
696
765
// currently we open nice url link and redirect then
@@ -741,52 +810,63 @@ function $LocationProvider(){
741
810
$browser . url ( $location . absUrl ( ) , true ) ;
742
811
}
743
812
744
- // update $location when $browser url changes
745
- $browser . onUrlChange ( function ( newUrl ) {
746
- if ( $location . absUrl ( ) != newUrl ) {
747
- $rootScope . $evalAsync ( function ( ) {
748
- var oldUrl = $location . absUrl ( ) ;
813
+ var initializing = true ;
749
814
750
- $location . $$parse ( newUrl ) ;
751
- if ( $rootScope . $broadcast ( '$locationChangeStart' , newUrl ,
752
- oldUrl ) . defaultPrevented ) {
753
- $location . $$parse ( oldUrl ) ;
754
- $browser . url ( oldUrl ) ;
755
- } else {
756
- afterLocationChange ( oldUrl ) ;
757
- }
758
- } ) ;
759
- if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
760
- }
815
+ // update $location when $browser url changes
816
+ $browser . onUrlChange ( function ( newUrl , newState ) {
817
+ $rootScope . $evalAsync ( function ( ) {
818
+ var oldUrl = $location . absUrl ( ) ;
819
+ var oldState = $location . $$state ;
820
+
821
+ $location . $$parse ( newUrl ) ;
822
+ $location . $$state = newState ;
823
+ if ( $rootScope . $broadcast ( '$locationChangeStart' , newUrl , oldUrl ,
824
+ newState , oldState ) . defaultPrevented ) {
825
+ $location . $$parse ( oldUrl ) ;
826
+ $location . $$state = oldState ;
827
+ setBrowserUrlWithFallback ( oldUrl , false , oldState ) ;
828
+ } else {
829
+ initializing = false ;
830
+ afterLocationChange ( oldUrl , oldState ) ;
831
+ }
832
+ } ) ;
833
+ if ( ! $rootScope . $$phase ) $rootScope . $digest ( ) ;
761
834
} ) ;
762
835
763
836
// update browser
764
- var changeCounter = 0 ;
765
837
$rootScope . $watch ( function $locationWatch ( ) {
766
838
var oldUrl = $browser . url ( ) ;
839
+ var oldState = $browser . state ( ) ;
767
840
var currentReplace = $location . $$replace ;
768
841
769
- if ( ! changeCounter || oldUrl != $location . absUrl ( ) ) {
770
- changeCounter ++ ;
842
+ if ( initializing || oldUrl !== $location . absUrl ( ) ||
843
+ ( $location . $$html5 && $sniffer . history && oldState !== $location . $$state ) ) {
844
+ initializing = false ;
845
+
771
846
$rootScope . $evalAsync ( function ( ) {
772
- if ( $rootScope . $broadcast ( '$locationChangeStart' , $location . absUrl ( ) , oldUrl ) .
773
- defaultPrevented ) {
847
+ if ( $rootScope . $broadcast ( '$locationChangeStart' , $location . absUrl ( ) , oldUrl ,
848
+ $location . $$state , oldState ) . defaultPrevented ) {
774
849
$location . $$parse ( oldUrl ) ;
850
+ $location . $$state = oldState ;
775
851
} else {
776
- $browser . url ( $location . absUrl ( ) , currentReplace ) ;
777
- afterLocationChange ( oldUrl ) ;
852
+ setBrowserUrlWithFallback ( $location . absUrl ( ) , currentReplace ,
853
+ oldState === $location . $$state ? null : $location . $$state ) ;
854
+ afterLocationChange ( oldUrl , oldState ) ;
778
855
}
779
856
} ) ;
780
857
}
858
+
781
859
$location . $$replace = false ;
782
860
783
- return changeCounter ;
861
+ // we don't need to return anything because $evalAsync will make the digest loop dirty when
862
+ // there is a change
784
863
} ) ;
785
864
786
865
return $location ;
787
866
788
- function afterLocationChange ( oldUrl ) {
789
- $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ) ;
867
+ function afterLocationChange ( oldUrl , oldState ) {
868
+ $rootScope . $broadcast ( '$locationChangeSuccess' , $location . absUrl ( ) , oldUrl ,
869
+ $location . $$state , oldState ) ;
790
870
}
791
871
} ] ;
792
872
}
0 commit comments