Search code examples
javascriptangularjsangular-routing

Angular JS: URL parameter appears before the `#!/` and doesn't appear in $location.search()


I'm integrating a small existing Angular JS app made by someone else into an exiting website that was not built with Angular.

All the Angular JS app files are kept in a completely separate directory and then the user can click on a link to take them to the index of that sub directory. For example:

<a href="/path/to/angular-app/?returnUrl=/original/location">Login.</a>

As you can see above, I need to pass a URL parameter called returnUrl. I then need to pick up this value in the Angular JS app, using:

$location.search()

However, this actually returns an empty object. I think it's because the Angular JS is appending #!/login on the URL after the URL parameter, like so:

example.com/path/to/app?returnUrl=/original/location/#!/login

If I modify the URL in the browser, moving the returnUrl parameter to the end of the URL and refresh the page, it works and $location.search() returns {returnUrl: "/original/location/"}. However, as far as I can see I'm not able to change this in my website because the parameter is part of the link to the Angular JS app and #!/login is added afterwards automatically.

I'm new AngularJS, so please explain as clearly as you can:

  1. What is going on here? And why is the URL parameter before the #!/login?
  2. Why does this prevent $location.search() from returning anything? If I move the param to the end of the resulting URL, it works.
  3. What can I do to fix this?

Solution

  • What is going on here? And why is the URL parameter before the #!/login?

    You can think of querystring parameters before the #! as a server-side parameters, and querystring parameters after the #! as client-side parameters. This is perfectly valid:

    http://example.com?serverParam=client1#!angularRoute?angularParam=someValue
    

    The server-side parameters allow you to configure how the page is served from the server and then, once server page is served and the Angular app is loaded, use the client-side parameters to supply what that particular Angular app is expecting.

    Why does this prevent $location.search() from returning anything? If I move the param to the end of the resulting URL, it works.

    Because nothing was specified for the client-side parameters until you moved the values there.

    What can I do to fix this?

    1. You could change the links to be like how you modified them to get them to work: <a href="example.com/path/to/app/#!/login?returnUrl=/original/location">Login.</a> or duplicate the parameter so it's available on both the server and the client <a href="example.com/path/to/app/?returnUrl=/original/location#!/login?returnUrl=/original/location">Login.</a>
    2. You can inject $window and get the server-side values from $window.location.search
    3. You can inject $location and use $location.absUrl()

    Here is an example of how you can set the client-side parameters from the server-side parameters:

    var app = angular.module('YOURAPPNAME', []);
    
    app.run(['$location', function($location) {
      // I'm going to fake out the url so it runs in the SnippetEditor.
      var url = "example.com/path/to/app?returnUrl=/original/location/#!/login"; //$location.absUrl();
      var hashbangIdx = url.indexOf('#!');
      var serverStr = hashbangIdx > -1 ? url.substring(0, hashbangIdx) : url;
      var qIdx = serverStr.indexOf("?");
      var p = qIdx > -1 ? serverStr.substring(qIdx).split(/[&||?]/) : [];
      for (var i = 0; i < p.length; i += 1) {
        if (p[i].indexOf('=') > -1) {
          var param = p[i].split('=');
          $location.search(param[0], param[1]);
        }
      }
    }]);
    
    app.controller('MainCtrl', ['$scope', '$location', function($scope, $location) {
      $scope.params = $location.search();
    }]);
    <!DOCTYPE html>
    <html ng-app="YOURAPPNAME">
    
    <head>
      <meta charset="utf-8" />
      <title>AngularJS</title>
      <script data-require="angular.js@1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
      <script src="app.js"></script>
    </head>
    
    <body ng-controller="MainCtrl">
      <h4>Parameters:</h4>
      <p ng-repeat="(name, val) in params">
        {{name}}: {{val}}
      </p>
    </body>
    
    </html>