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:
#!/login
?$location.search()
from returning anything? If I move the param to the end of the resulting URL, it works.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?
<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>
$window
and get the server-side values from
$window.location.search
$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="[email protected]" 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>
You just need to use window.location.search
> window.location.search
> "?returnUrl=/original/location/"
$location.search()
gives you values only if your route has defined it.
Here are the answers to your questions:
1) AngularJS appends the # at the end of the url at which the page was loaded. So, if the url to load the page already had the query parameter of returnUrl, which seems to be the case, it will always show up to the left of the #.
2) An AngularJS application, is generally only concerned about what occurs after the # in the url. AngularJS uses this for routing and passing query parameters between states and more similar stuff. The $location.search() is meant to retrieve all the query parameters after the #.
3) Here is how you can extract the query parameter that occurs before the #
function getParamFromURL (paramName) {
var searchStr = window.location.search.substring(1);
var keyValues = searchStr.split('&');
for(var i=0; i<keyValues.length; i++) {
var split = keyValues[i].split('=');
if(split[0] === paramName) {
return decodeURIComponent(split[1]);
}
}
}
var value = getParamFromURL('returnUrl');
console.log(value);
The #
you're seeing in the url is actually what's called an anchor link, or fragment. It's usually done to jump to a specific element by its id, for example navigating clicking on a <a href="#footer">
will directly scroll your page if you have an element matching this id.
Angular and other frameworks, like React, make use of this functionality to create what's called a hash routing by using the hash as the current active route. The advantages of doing so is that it will allow for a very quick setup and it will work out-of-the-box on non-HTML5 browsers.
Regarding why the query parameter is before the hash, it's only because that's actually what you specified in your link. Angular considers the url you are loading as the root of the project and adds its own #
with the current router after its initialization. You could potentially have a server that serves the Angular app only while specifying this query parameter, thus the importance of keeping it in place. If the query param is place after the hash, it's considered as part of the Angular app routing, thus returned when you call $location.search()
.
What I would advice you to do is either explicitly specifying the #
in your link, or use html5 mode. Html5 mode means that you won't get the hash form in your url, rather "normal" ones that are less ugly.
To do that, you'll have to enable html5 mode in your app,
.config(function ($locationProvider) {
$locationProvider.html5Mode(true);
}
And add the meta base in your head
<base href="/" />
The thing is that now, if you try to load a page that is not the home, you'll get into a 404 issue.
To prevent that, you'll have to serve your Angular entry for each subroute of your application. There is multiple ways, depending on what you use/ want to use on the server. Here is the Angular FAQ that showcases most of them.
You can use $location.absUrl() : It will return full URL with all segments encoded according to rules specified in RFC 3986.
var resultURl = GetURLParameter('returnUrl');
console.log(resultURl);
function GetURLParameter(param) {
// var URL = $location.absUrl();
var URL = "example.com/path/to/app?returnUrl=/original/location/#!/login";
var allVariables = URL.split('?');
for (var i = 0; i < allVariables.length; i++) {
var paramName = allVariables[i].split('=');
if (paramName[0] == param) {
return paramName[1].split("#")[0];
}
}
}
============================== OR ====================================
RouteProvider
and routeParams
will work in your situation.
You can pass the returnUrl
as a route param in the routing and get back into the controller using $routeParams.returnUrl
.
What can I do to fix this?
You can try this approach to fix this.
HTML :
<a href="#login/original/location">Login Return Url</a>
Routing file :
$routeProvider.when('/login/:returnUrl', {
templateUrl: 'partials/partial.html',
controller: 'MyCtrl'
});
In controller, inject $routeParams
:
.controller('MyCtrl', ['$scope','$routeParams', function($scope, $routeParams) {
var url = $routeParams.returnUrl;
}]);
Note :
If you're using ngRoute
, you can inject $routeParams into your controller.
If you're using angular-ui-router
, you can inject $stateParams into your controller.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With