$location Search-Parameter Data Type Depends On Source In AngularJS
In AngularJS, the $location service provides a two-way relationship between the browser's URL and the model in your application. This means that the URL can change the model and, conversely, the model can change the URL. This creates an interesting situation in which query-string parameters (aka, search parameters) can come from two different places: the URL or the Controller. And, as it turns out, the source of the search parameter defines the data type of the value in the $location service. When you're dealing with strings, this doesn't matter; but, when you're dealing with numbers or Booleans, this can lead to unexpected behaviors.
Run this demo in my JavaScript Demos project on GitHub.
The browser's URL is represented by a set of String values. And, when AngularJS listens for changes on the URL, it has to parse those strings and translate them into a set of $location values. When it does this, it has no philosophy on data-types. All values coming out of the URL are strings. It doesn't look at the value "3", for example, and assume that the application actually wants that value as the number 3 - it just parses and compiles strings.
NOTE: The $location service provides special treatment for True values, including them in the URL by name-only (less a value) and properly reporting them as Booleans when parsed. This treatment does not apply to False values.
However, when the $location is updated programmatically, by the Controller, the inputs can have a variety of data types. AngularJS then translates your various typed-inputs into String values which it can use to update the URL.
This means that if a search value is driven by the URL, it will be a String. And, if it is driven by the Controller, it may be a string; but, it may also be a number or a Boolean or something else altogether. This becomes a hurdle if you are attempting to use strict-equality. To see this in action, take a look at the following demo.
In this code, I am setting and getting a userID value. With the first two links, I am using the URL to drive the userID. With the third link, I am using the Controller to drive the $location, which drives the URL. Then, as the URL changes, I'm inspecting the $location.search() value to various types of equality.
<!doctype html>
<html ng-app="Demo">
<head>
<meta charset="utf-8" />
<title>
$location Search-Parameter Data Type Depends On Source In AngularJS
</title>
<link rel="stylesheet" type="text/css" href="./demo.css"></link>
</head>
<body ng-controller="AppController">
<h1>
$location Search-Parameter Data Type Depends On Source In AngularJS
</h1>
<p>
<!--
With these first two links, the URL is going to drive the setting of
the userID value. Meaning, the view-model will be updated to reflect
the state of the URL.
-->
<a href="#/page?userID=1">#/page?userID=1</a>
|
<a href="#/page?userID=2">#/page?userID=2</a>
|
<!--
With this last link, the view-model is going to drive setting the
userID value. Meaning, the URL will be updated to reflect the state
of the view-model.
-->
<a ng-click="setUserID()">Set Programmatically</a>
</p>
<p>
<strong>Current userID</strong>: {{ currentUserID }}
</p>
<!-- Load scripts. -->
<script type="text/javascript" src="../../vendor/angularjs/angular-1.3.15.min.js"></script>
<script type="text/javascript">
// Create an application module for our demo.
var app = angular.module( "Demo", [] );
// -------------------------------------------------- //
// -------------------------------------------------- //
// I control the root of the application.
app.controller(
"AppController",
function( $scope, $location ) {
$scope.currentUserID = null;
// Listen for changes on the location and inspect the values.
$scope.$on(
"$locationChangeStart",
function handleLocationChangeEvent( event ) {
// Pluck the userID out of the search - this is how we can
// access the value regardless of whether it was driven by the
// URL or driven by a programmatic change.
var userID = $scope.currentUserID = $location.search().userID;
var data = [];
for ( var i = 0 ; i <= 5 ; i++ ) {
// Check the userID against the index using both strict
// quality (===) and loose equality (==).
data.push({
userID: userID,
userIDType: ( typeof userID ),
index: i,
strict: ( ( userID === i ) ? "STRICT EQUALITY" : "-" ),
loose: ( ( userID == i ) ? "LOOSE EQUALITY" : "-" )
});
}
console.table(
data,
[ "index", "userID", "userIDType", "strict", "loose" ]
);
}
)
// ---
// PUBLIC METHODS.
// ---
// I programmatically set the userID, which will drive the URL.
$scope.setUserID = function() {
// NOTE: We are setting the value as a NUMBER LITERAL.
$location.search( "userID", 3 );
};
}
);
</script>
</body>
</html>
As you can see, I'm checking the userID against a known number (the for-loop index) to see if they are equal using both strict and loose equality. And, when we run the above code and toggle through the links, we get the following output:
As you can see, the userID value is always a String when it comes out of the URL. This is why strict-equality fails. However, if the userID is defined by the Controller (which subsequently drives the URL change), the userID is a Number and both strict and loose equality work.
The other catch to watch out for is assuming URL values map nicely onto "Truthy" and "Falsey" values. For example, take the query-string snippet:
&showAlert=false&
Given the state of the URL, you might be templed to treat $location.search().showAlert as a Falsey value; however, if the value is being driven by the URL, you would be mistaken. If the value is driven by the URL, then the value of showAlert will be the string "false", not the Boolean literal. And, any non-empty string is actually a Truthy, not a Falsey.
Now, I am definitely not advocating that you ditch strict-equality when dealing with the $location.search() data - I do love me some strict-equality. I'm just pointing out that you have to be cognizant of where data might be coming from and how that source affects the manifestation of that data. The more you know!
Want to use code from this post? Check out the license.
Reader Comments