I'd like to read the values of URL query parameters using AngularJS. I'm accessing the HTML with the following URL:
http://127.0.0.1:8080/test.html?target=bob
As expected, location.search is "?target=bob".
For accessing the value of target, I've found various examples listed on the web, but none of them work in AngularJS 1.0.0rc10. In particular, the following are all undefined:
$location.search.target
$location.search['target']
$location.search()['target']
Anyone know what will work? (I'm using $location as a parameter to my controller)
Update:
I've posted a solution below, but I'm not entirely satisfied with it.
The documentation at Developer Guide: Angular Services: Using $location states the following about $location:
When should I use $location?
Any time your application needs to react to a change in the current
URL or if you want to change the current URL in the browser.
For my scenario, my page will be opened from an external webpage with a query parameter, so I'm not "reacting to a change in the current URL" per se. So maybe $location isn't the right tool for the job (for the ugly details, see my answer below). I've therefore changed the title of this question from "How to read query parameters in AngularJS using $location?" to "What's the most concise way to read query parameters in AngularJS?". Obviously I could just use javascript and regular expression to parse location.search, but going that low-level for something so basic really offends my programmer sensibilities.
So: is there a better way to use $location than I do in my answer, or is there a concise alternate?
You can inject $routeParams (requires ngRoute) into your controller. Here's an example from the docs:
// Given:
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
//
// Then
$routeParams ==> {chapterId:1, sectionId:2, search:'moby'}
EDIT: You can also get and set query parameters with the $location service (available in ng), particularly its search method: $location.search().
$routeParams are less useful after the controller's initial load; $location.search() can be called anytime.
Good that you've managed to get it working with the html5 mode but it is also possible to make it work in the hashbang mode.
You could simply use:
$location.search().target
to get access to the 'target' search param.
For the reference, here is the working jsFiddle: http://web.archive.org/web/20130317065234/http://jsfiddle.net/PHnLb/7/
var myApp = angular.module('myApp', []);
function MyCtrl($scope, $location) {
$scope.location = $location;
$scope.$watch('location.search()', function() {
$scope.target = ($location.search()).target;
}, true);
$scope.changeTarget = function(name) {
$location.search('target', name);
}
}
<div ng-controller="MyCtrl">
Bob
Paul
<hr/>
URL 'target' param getter: {{target}}<br>
Full url: {{location.absUrl()}}
<hr/>
<button ng-click="changeTarget('Pawel')">target=Pawel</button>
</div>
To give a partial answer my own question, here is a working sample for HTML5 browsers:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<script src="http://code.angularjs.org/1.0.0rc10/angular-1.0.0rc10.js"></script>
<script>
angular.module('myApp', [], function($locationProvider) {
$locationProvider.html5Mode(true);
});
function QueryCntl($scope, $location) {
$scope.target = $location.search()['target'];
}
</script>
</head>
<body ng-controller="QueryCntl">
Target: {{target}}<br/>
</body>
</html>
The key was to call $locationProvider.html5Mode(true); as done above. It now works when opening http://127.0.0.1:8080/test.html?target=bob. I'm not happy about the fact that it won't work in older browsers, but I might use this approach anyway.
An alternative that would work with older browsers would be to drop the html5mode(true) call and use the following address with hash+slash instead:
http://127.0.0.1:8080/test.html#/?target=bob
The relevant documentation is at Developer Guide: Angular Services: Using $location (strange that my google search didn't find this...).
It can be done by two ways:
Using $routeParams
Best and recommended solution is to use $routeParams into your controller.
It Requires the ngRoute module to be installed.
function MyController($scope, $routeParams) {
// URL: http://server.com/index.html#/Chapter/1/Section/2?search=moby
// Route: /Chapter/:chapterId/Section/:sectionId
// $routeParams ==> {chapterId:'1', sectionId:'2', search:'moby'}
var search = $routeParams.search;
}
Using $location.search().
There is a caveat here. It will work only with HTML5 mode. By default, it does not work for the URL which does not have hash(#) in it http://localhost/test?param1=abc¶m2=def
You can make it work by adding #/ in the URL. http://localhost/test#/?param1=abc¶m2=def
$location.search() to return an object like:
{
param1: 'abc',
param2: 'def'
}
$location.search() will work only with HTML5 mode turned on and only on supporting browser.
This will work always:
$window.location.search
Just to summerize .
If your app is being loaded from external links then angular wont detect this as a URL change so $loaction.search() would give you an empty object . To solve this you need to set following in your app config(app.js)
.config(['$routeProvider', '$locationProvider', function ($routeProvider, $locationProvider)
{
$routeProvider
.when('/', {
templateUrl: 'views/main.html',
controller: 'MainCtrl'
})
.otherwise({
redirectTo: '/'
});
$locationProvider.html5Mode(true);
}]);
Just a precision to Ellis Whitehead's answer. $locationProvider.html5Mode(true); won't work with new version of angularjs without specifying the base URL for the application with a <base href=""> tag or setting the parameter requireBase to false
From the doc :
If you configure $location to use html5Mode (history.pushState), you need to specify the base URL for the application with a tag or configure $locationProvider to not require a base tag by passing a definition object with requireBase:false to $locationProvider.html5Mode():
$locationProvider.html5Mode({
enabled: true,
requireBase: false
});
you could also use $location.$$search.yourparameter
I found that for an SPA HTML5Mode causes lots of 404 error problems, and it is not necessary to make $location.search work in this case. In my case I want to capture a URL query string parameter when a user comes to my site, regardless of which "page" they initially link to, AND be able to send them to that page once they log in. So I just capture all that stuff in app.run
$rootScope.$on('$stateChangeStart', function (e, toState, toParams, fromState, fromParams) {
if (fromState.name === "") {
e.preventDefault();
$rootScope.initialPage = toState.name;
$rootScope.initialParams = toParams;
return;
}
if ($location.search().hasOwnProperty('role')) {
$rootScope.roleParameter = $location.search()['role'];
}
...
}
then later after login I can say
$state.go($rootScope.initialPage, $rootScope.initialParams)
It's a bit late, but I think your problem was your URL. If instead of
http://127.0.0.1:8080/test.html?target=bob
you had
http://127.0.0.1:8080/test.html#/?target=bob
I'm pretty sure it would have worked. Angular is really picky about its #/
Related
I can not get rid of a small issue affecting my app functionality to enable to call a phone number using skype, what I have so far is this:
HTML:
<a ng-href="skype:{{user.phone}}?call" class="btn btn-sm btn-success" type="button">{{user.phone}}</a>
ANGULAR
(function() {
angular.module('core').config(coreConfig);
coreConfig.$inject = ['$compileProvider'];
function coreConfig($compileProvider) {
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|tel|file|skype):/);
}
})();
The result is always the same, when I hover the element to start a call the browser show this: unsafe:skype:012345678?call and do not allow me to call the number...
I added the config part browsing other questions related to similar issues but it doesn't solve my issue.
EDIT:
I'm using meanjs.org
EDIT 2:
Please do not copy/paste my question code as your answer... I know that it work on a normal Angular application. The problem is that I can not let it work using meanjs.org app. Thanks.
EDIT 3:
I just found that: if I use the skype link in the main root / or in a child root like: /list it work fine without adding the unsafe prefix. In a dynamic root like: /list/1234 it doesn't work anymore. I don't know if it could help.
The "bug" was caused by an old version of ngFileUpload that overwrite my configuration as reported here.
Upgrading the version of ngFileUpload solved my issue.
Thanks for your help.
You need to explicitly add URL protocols to Angular's whitelist using a regular expression. Only http, https, ftp and mailto are enabled by default. Angular will prefix a non-whitelisted URL with unsafe: when using a protocol such as chrome-extension:.
A good place to whitelist the chrome-extension: protocol would be in your module's config block:
var app = angular.module( 'myApp', [] )
.config( [
'$compileProvider',
function( $compileProvider )
{
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|mailto|chrome-extension):/);
// Angular before v1.2 uses $compileProvider.urlSanitizationWhitelist(...)
}
]);
The same procedure also applies when you need to use protocols such as file: and tel:.
Please see the AngularJS $compileProvider API documentation for more info.
I've made it work in following plunker
http://plnkr.co/edit/rIPFz9WhFjlRJ1ogCArP?p=preview
angular.module('plunker', [])
.config(function($compileProvider){
$compileProvider.aHrefSanitizationWhitelist(/^\s*(https?|ftp|skype|mailto|tel|file):/)
})
.controller('MainCtrl', function($scope) {
$scope.href = 'skype:123456?call';
});
I am trying to so a angular redirect however I have only got part of the way. At the moment I have managed to change the URL however I want to then go to that url instead of just changing it.
scope.socialReturnUrl = function ( path ) {
location.url( path + 'assessment' );
};
My below code seems to only change the URL or the location. What am I missing to apply this so it gets redirected?
I should point out that I've already tried to use scope$apply() however this causes the below error:
scope$apply is not defined
I already have scope as a dependency but I havent got a apply dependency is this something that I need?
Let me know your thoughts!
You need to inject the $location service in your controller in order to change the url. If you don't, you'll be calling the window.location object instead, which is not what you should be doing.
var app = angular.module('myApp');
app.controller('MyController',
['$scope', '$location',
function($scope, $location) {
$scope.socialReturnUrl = function ( path ) {
$location.url( path + 'assessment' );
};
}])
Right... I fixed this myself.
Apparently location.url() will not do a full refresh as seen in the documentation.
https://docs.angularjs.org/guide/$location
However you need to add the dependency $window and use the below code to make the page do a full page reload.
window.location.href = path + 'assessment';
EDIT The code is all correct, the problem was because of including 'ngTouch', see my own answer below.
I am probably making some stupid mistake here, but for the life of me I cannot find it.
I have this piece of markup, which is correctly wired up with a controller:
<input type="text" ng-change="doStuff()" ng-model="stuff"/>
<button ng-click="doStuff()">doStuff</button>
The controller code:
console.log('Hi from controller');
$scope.stuff = "something";
$scope.doStuff = function() {
alert('doing stuff');
}
The problem is nothing happens when I click the button. If I change the input field, I get the alert, so ng-change works, but ng-click does not.
Let me know if this is not enough information, but I would not know what else to provide, and the general setup seems to be working fine...
The rest of the HTML does not contain any Angular directives, and it is loaded like this in myModule.config:
$stateProvider
.state('stuff', {
templateUrl: 'pages/stuff.html',
url: '/stuff',
controller: 'StuffCtrl'
})
and the controller is defined like this:
angular.module('myModule')
.controller('StuffCtrl', function ($scope) {
// above code
});
It turned out that the problem was with another dependency 'ngTouch'. I did not use it, but still it was loaded. My module did not even depend on it. (I am using that admin site template from here: http://startangular.com/product/sb-admin-angular-theme/). After removing loading of the ngTouch it worked as expected. I will file this as a bug to both projects as well... Thanks for your help!
I have angularJS(AngularJS v1.3.0-beta.3) application that crashes in IE10 compatibility mode. It works fine in FF, Chrome and IE11. Here is what I get as an error in console:
Multiple directives [login, login] asking for 'login' controller on: <div>
to set state of application, I create a node:
link: function ($scope, $element, $attrs) {
....
$element.html('<login></login>');
$compile($element.contents())($scope); // crash happens here
....
}
Here is my login directive:
widgets.directive('login', ['$compile', '$http', 'resourceLoader', function ($compile, $http, resourceLoader) {
return {
restrict: 'AE',
replace: true,
template: '<div></div>',
controller: function ($scope, $element) {
$scope.user.isLogged = false;
$scope.user.password = undefined;
$scope.submitLogin = function () {
// code that goes to server
};
},
link: function ($scope, $element, $attrs) {
resourceLoader.get('templates', 'profile', 'unlogged/login', 'jquery.min', function (template) {
$element.html(template);
$compile($element.contents())($scope);
});
}
};
}]);
Any ideas? Thanx.
The main issue is Angular 1.3 does not support older versions of Internet Explorer, more specifically IE8 and less. Putting IE10 in compatibility mode will make the browser act as if it were an older browser for certain layouts and features. The backwards compatability issues are likely the culprit here.
The suggestion by Angular is to remain in a version less than 1.3 to ensure compatability.
References:
See Angular's post on the 1.3 update and review Compatibility Mode settings for further reading on the issues.
Have you tried changing the restriction on the directive from EA to just E, or (probably better for compatability) just A and then using <div data-login="true"></div>?
It looks like something strange is going on with how the html is being parsed - I expect that it's probably adding an attribute for its own use in compatibility, which is screwing everything up.
If this doesn't work, you'd be much more likely to get a correct answer if you provide a plunker or a fiddle to demonstrate the issue more clearly.
Add this line
if ( name === 'login' ) console.log(name, directiveFactory.toString());
at this line
If it prints out twice, you are really loading the directive twice. With the directiveFactory's source code printed out, you will also see if it's the same directive loaded twice or two directives with the same name.
Give id="ng-app" where you are assigning your module name ng-app="module". That will support IE.
Adding below line in index.html's head section solved my problem:
<meta http-equiv="x-ua-compatible" content="IE=edge">
For more info : https://stackoverflow.com/a/46489212/698127
I think what I am trying to do is fairly simple, but I can't seem to determine the correct architecture using Angular.
I'd like to have a MessageCenter, so that any controller or other piece of Angular code can add a message. I thought that the correct way to do this would be with a MessageCenter service. I would inject the service where needed, and call MessageCenter.add(). The problem that I am having is how to compile the cloned DOM directive, since $compile requires a $scope, which I do not have from a service.
Here is what I am trying. I am not sure what the correct approach is for this.
<div>
<div id="msg-proto" message-center-msg class="alert alert-success"></div>
</div>
Then:
.factory('MessageCenter', function($compile) {
var that = {}
that.add = function(type, message) {
var n = $("#msg-proto"),
n2 = n.clone();
n.parent().append($compile(n2));
}
return that;
});
And I am injecting and calling the service:
.controller('SomeCtrl', function(MessageCenter) {
MessageCenter.add('some msg');
});
I have a message-center-msg directive defined that adds some behaviors to the element. But it needs to be $compile'd in order for that to happen, and I am not sure how to make this all work. I only ever end up with "Error: Argument 'scope' is required" from the service.
How can I setup a global message center, that is able to clone/add messages and have the directive on the message dom element processed?
Your service shouldn't interact with the DOM directly as you're trying to do. If I were designing such a system I would:
Have a service which simply takes in messages and either puts them in an array (if you want to support a whole list of messages) or simply remembers the last message (if you only want to support a single message)
Have a controller which gets the message service injected to it and binds the message(s) from the service into its scope
Then have a bit of HTML (or a custom directive) that iterates the messages using the above controller and displays them
Here's what I mean (plunk here: http://plnkr.co/Eps1Gy)
index.html:
<!doctype html>
<html>
<head>
<script src="bower_components/angular/angular.js"></script>
<script src="scripts/app.js"></script>
<script src="scripts/controllers/messages.js"></script>
<script src="scripts/controllers/click.js"></script>
<script src="scripts/services/messageService.js"></script>
</head>
<body ng-app="messagesApp">
<h1>Messages</h1>
<div ng-controller="MessagesCtrl">
<ul ng-repeat="message in messages">
<li>{{message}}</li>
</ul>
</div>
<a ng-href="#" ng-controller="ClickCtrl" ng-click="addMessage()" >Add a new message!</a>
</body>
</html>
messageService.js:
function MessageService() {
this.messages = [];
this.add = function(msg) {
this.messages.push(msg);
}
}
angular.module('messagesApp')
.service('messageService', MessageService);
messages.js:
angular.module('messagesApp')
.controller('MessagesCtrl', function ($scope, messageService) {
$scope.messages = messageService.messages;
});
click.js:
angular.module('messagesApp')
.controller('ClickCtrl', function ($scope, messageService) {
var count = 0;
$scope.addMessage = function() {
messageService.add('Test message ' + count++);
}
});
app.js:
angular.module('messagesApp', [])
.config(function ($routeProvider) {
$routeProvider
.otherwise({
redirectTo: '/'
});
});
Like I said, you can replace the HTML for handling the messages with a custom directive if you want some complex interaction, but the important thing is that you don't try to muck with the view from your service. The service should interact only with a well-defined model.
I implemented something pretty identical a while back and recently made this public as a bower component.
Maybe you can use or work off of it: https://github.com/IanShoe/angular-message-center
Happy coding!
You can open your mind :)
On your call to the MessageCenter move the $scope as parameter like MessageCenter.add("", $scope);
Or ... you can expose the MessageCenter to the scope globally on startup something like this guy suggested