48

I'm trying to write my first web-app with Angular.

In the normal mode (html5Mode off), Angular forces the address's hash part to look like a "path" (adding a leading "/"), and encodes special characters - for example, it allows a single "?" and "#" in the hash and replaces the others with %3F and %23.

Is there a way to turn this feature off? I don't want to use the $locationProvider / $routeProvider features - I want to parse the hash myself (In my case, the user's will enter some "free text" in the hash to search inside my website).

I read that the routeProvider cannot be configured to use regular expressions...

If htmlMode is turned on, then the address's hash part is not forced to look like a path (no leading "/"), but it still encodes special characters.

I'm aware that some browsers might encode/escape the special characters anyway, but if the user managed to enter some special characters in its address bar then I don't want to change it.

Thanks

Oren
  • 2,767
  • 3
  • 25
  • 37

5 Answers5

68

Not sure of the side effects of this, but it gets the job done. Note that it will disable all location manipulation from the angular app, even if intended.

angular.module('sample', [])
    .config( ['$provide', function ($provide){
        $provide.decorator('$browser', ['$delegate', function ($delegate) {
            $delegate.onUrlChange = function () {};
            $delegate.url = function () { return ""};
            return $delegate;
        }]);
    }]);

ES6 variant:

angular.module('sample', [])
    .config(["$provide", $provide => {
        $provide.decorator("$browser", ["$delegate", $delegate => {
            $delegate.onUrlChange = () => { };
            $delegate.url = () => "";

            return $delegate;
        }]);
    }]);

Tested in Chrome 30, IE9, IE10.
Inspired by https://stackoverflow.com/a/16678065/369724

Cerbrus
  • 70,800
  • 18
  • 132
  • 147
Grae Kindel
  • 2,664
  • 1
  • 24
  • 18
  • Huh, I gave up this feature idea a long time ago... Just for the record, I'm accepting your answer even though I haven't tested it. – Oren Nov 07 '13 at 01:12
  • 1
    Thanks. Hopefully someone on the angularjs team will stumble across this and tell me that there is a right way. This solution took much trial/error and wasn't found until I started reading angular's source code. Customer is now happy, and I'm going to scrap the framework as so soon as I have time. It is an impressive toy, but has proven impractical to learn and extend. – Grae Kindel Nov 07 '13 at 02:28
  • 1
    Angular retries to manipulate the location which leads to an infinite loop. – gronke Dec 28 '13 at 21:39
  • 1
    This code fails when you use ng-include anywhere on the page. It simply stops working and throws plenty of errors. – kazy Oct 14 '14 at 11:48
  • 1
    This appears to be a good workaround for angularjs 1.4.3. ng-include works as well. – lestersconyers Sep 28 '15 at 05:03
  • 1
    Worked perfect for me, when used angular with PJAX in same project! – Elya Livshitz Nov 11 '15 at 04:45
  • Savior! It took ages to find this, but this is exactly what I needed. – Cerbrus Aug 03 '18 at 13:23
5

I use a local copy of angular.js. Search for

$browser.onUrlChange(function(newUrl, newState) {

and

$rootScope.$watch(function $locationWatch() {

comment out the corresponding lines and angularjs will stop watch for location url changes.

Yang Zhang
  • 4,540
  • 4
  • 37
  • 34
2

Thank @greg.kindel 's answer, you help me find a solution to solve anchor problem. This code let AngularJS app IGNORE some hash pattern, keep it working like browser default. I don't need enable html5Mode, and ngRoute still working. :)

app.config(['$provide', function ($provide) {
    $provide.decorator('$browser', ['$delegate', '$window', function ($delegate, $window) {
        // normal anchors
        let ignoredPattern = /^#[a-zA-Z0-9].*/;
        let originalOnUrlChange = $delegate.onUrlChange;
        $delegate.onUrlChange = function (...args) {
            if (ignoredPattern.test($window.location.hash)) return;
            originalOnUrlChange.apply($delegate, args);
        };
        let originalUrl = $delegate.url;
        $delegate.url = function (...args) {
            if (ignoredPattern.test($window.location.hash)) return $window.location.href;
            return originalUrl.apply($delegate, args);
        };
        return $delegate;
    }]);
}]);

Tested in Chrome 69, Firefox 62

AngularJS 1.7.4

Dragon
  • 126
  • 1
  • 3
2

The workaround from @greg.kindel (the accepted solution) didn't work for me. It threw lots of errors about an infinite digest loop. I'm using Angular 1.5.8.

I was able to adjust that workaround to the following to get things working. I hope it saves someone else grief.

angular.module('sample', [])
  .config(['$provide', function ($provide) {
    $provide.decorator('$browser', ['$delegate', '$window', function ($delegate, $window) {
      $delegate.onUrlChange = function () {};
      //
      // HACK to stop Angular routing from manipulating the URL
      //
      // The url() function seems to get used in two different modes.
      //
      // Mode 1 - Zero arguments
      // There are no arguments given, in which case it appears that the caller is expected the
      // browser's current URL (a string response).
      //
      // Mode 2 - Three arguments
      // It receives three arguments (url, some_boolean, null).  It seems the caller is expecting
      // the browser's URL to be updated to the given value.  The result of this function call is
      // expected to be the $delegate itself, which will subsequently get called with no arguments
      // to check the browser's URL.
      //
      // The Hack:
      // We save the URL so that we can lie to the caller that the URL has been updated to what was
      // requested, but in reality, we'll do nothing to alter the URL.
      //
      var savedUrl = null
      $delegate.url = function (url, ...args) {
        if (!!url) {
          savedUrl = url;
          return $delegate;
        } else {
          return !!savedUrl ? savedUrl : $window.location.href;
        }
      };
      return $delegate;
    }]);
  }])
DJ Jain
  • 21
  • 1
1

If I recall correctly, Angular's routing is not obligatory, but then you must take care of reloading controllers, views, etc.

Adam
  • 2,897
  • 2
  • 23
  • 23
  • 2
    It's not obligatory, but it is always on. You'll see this if you have a page (foo.html#abc). If you reload, it becomes (foo.html#/abc). The conflict I had was with the window.history API failing when going pushState(...);back();forward(); because angular was replacing the url so the forward history was lost --even though I wasn't using routing at all in the angularjs part of the page. – Grae Kindel Nov 07 '13 at 02:22
  • I would try custom Angular build, though I've never done this myself :) The devil is in the `src/ng/location.js`. Reference to this file should be removed from `angularFiles.js` so that Grunt will not package it. I suppose you should also remove reference to `$LocationProvider` from `src/AngularPublic.js` to avoid errors. – Adam Nov 13 '13 at 23:29