Case insensitivity with angularjs ui-router - angularjs

I'm building a new angularJS app, based from the AngularJS SPA Visual studio template (http://visualstudiogallery.msdn.microsoft.com/5af151b2-9ed2-4809-bfe8-27566bfe7d83)
this uses ui-router (https://github.com/angular-ui/ui-router) for its routing.
however, it seems to be case sensitive.
Any idea how I would instruct angular/ui-router to ignore the case of the url parameter?
case sensitivity doesn't matter while in the app, though should a user type a url to enter the application at a specific page, we need to ensure that about is also the same as aBouT
Cheers

You can now configure ui-router to be case insensitive directly. Here is how you can use it:
angular.module('main', ['ui.router']);
angular.module('main').config(['$urlMatcherFactoryProvider', '$stateProvider', '$urlRouterProvider',
function($urlMatcherFactory, $stateProvider, $urlRouter) {
$urlMatcherFactory.caseInsensitive(true);
$urlMatcherFactory.strictMode(false);
$stateProvider.state('foo', {
url: '/foo',
template: '<b>The Foo View</b>'
});
$stateProvider.state('bar', {
url: '/bar',
template: '<b>The Bar View</b>'
});
$stateProvider.state('nomatch', {
url: '/nomatch',
template: '<b>No match found View</b>'
});
$urlRouter.otherwise('/nomatch');
}
]);
In the latest release (0.2.11), this is broken. A fix has been pushed already that can be seen at Github. So, currently, the best solution is to clone ui-router and build the head of master manually. Alternatively, you can just alter the source manually until the next release comes.
UPDATE (11/18/2014):
A release has now been made that incorporates the fix from above so that you no longer have to pull source and build manually. You can view the release on Github or just get the latest build.

Following the link in the comments to the original question, i was able to get the answer I needed.
Before my $stateProvider.state(......) routes I now have this piece of code:
$urlRouterProvider.rule(function ($injector, $location) {
//what this function returns will be set as the $location.url
var path = $location.path(), normalized = path.toLowerCase();
if (path != normalized) {
//instead of returning a new url string, I'll just change the $location.path directly so I don't have to worry about constructing a new url string and so a new state change is not triggered
$location.replace().path(normalized);
}
// because we've returned nothing, no state change occurs
});
Essentially it will toLowerCase() a url that isn't all lowercase already.
Once done, it replaces the url rather than redirects. Then carries on with matching a state.

You shouldn't change how ui-route handles URL matching to accept case insensitive URLs (that will have unexpected problems), but you can attempt to correct URLs for the user automatically when the routes fail.
When ui-route can not match a URL to a route it triggers the otherWise() callback. I'll show you have to redirect using this callback.
The following makes the assumption that all URLs for your app should be in lower case.
var stateHandler = function($urlRouterProvider)
{
$urlRouterProvider.otherwise(function($injector, $location)
{
var url = $location.absUrl();
var redirect = url.toLowerCase();
if(url == redirect)
{
return;
}
$window.location = redirect;
});
};
YourAngularApp.config(['$urlRouterProvider',stateHandler]);
If you need more control, then use a regex to select which URLs need rewriting.

According to official wiki,
https://github.com/angular-ui/ui-router/wiki/URL-Routing
Darren's answer looks right:
app.config(function ($urlRouterProvider) {
// Here's an example of how you might allow case insensitive urls
$urlRouterProvider.rule(function ($injector, $location) {
//what this function returns will be set as the $location.url
var path = $location.path(), normalized = path.toLowerCase();
if (path != normalized) {
//instead of returning a new url string, I'll just change the $location.path directly so I don't have to worry about constructing a new url string and so a new state change is not triggered
$location.replace().path(normalized);
}
// because we've returned nothing, no state change occurs
});}

Related

AngularJs ui-router reload template

I have a angularjs project that's using ui-router for routing.
I am using $state.reload() to reload the current state and it works just fine except that in my development system i want the $state.reload() to also reload the html template to reflect the new changes without a full page reload.
Is there a hack or option to do this ?
Update :
Chris T's solution almost worked, but i have templateFactoryDecorator witch adds a cache buster to the template url.
function configureTemplateFactory($provide) {
// Set a suffix outside the decorator function
var cacheBuster = Date.now().toString();
function templateFactoryDecorator($delegate) {
var fromUrl = angular.bind($delegate, $delegate.fromUrl);
$delegate.fromUrl = function (url, params) {
if (url !== null && angular.isDefined(url) && angular.isString(url)) {
url += (url.indexOf("?") === -1 ? "?" : "&");
url += "v=" + cacheBuster;
}
return fromUrl(url, params);
};
return $delegate;
}
$provide.decorator('$templateFactory', ['$delegate', templateFactoryDecorator]);
}
so
$templateCache.remove($state.current.templateUrl);
didn't work and i had to use
$templateCache.removeAll();
Its not ideal but for development environment its ok.
Internally, UI-Router leverages the $templateCache service to avoid re-fetching templates. Before calling $state.reload(), clear out the template you want to refetch from the server.
Assuming you want to do this from a controller:
function MyController($scope, $templateCache, $state) {
$scope.reload = function() {
$templateCache.remove("/path/to/template.html");
$state.reload();
}
}
Docs:
$templateCache
$cacheFactory
On a side note, I find it odd that you would want to refetch the template from the server.
I may have missed your point but When you use $state.reload() it reloads the state that means all the partials in side that template and the controllers in side that controller scope. Thus the state is reloaded not the full page.
So I see no errors in your side unless you want to do other staff ..
And if you are dynamically generating the DOM, It will do it for you .

Get previous URL on Angular request interceptor

I'm implementing an angular page-wide authentication app.
It is an event machine that uses an interceptor, so if any request results in a 401 response, it pops a login modal. When the login succeeds, it performs the request again (which could as well result in a 403 if the logged user do not have enough privileges to access the requested resource).
So long, things are working as expected except that when the user cancels the login process, the $location.path() still points to the requested resource. I want to revert this to the previous path. So:
What I expected
I expected to be able to get the "callee" URL like so:
.factory('authInterceptor', ...
return {
// Append the authentication headers to every request
request: function(config) {
previousLocation = $location.path(); // <--- HERE
if(Session.exist) {
config.headers.Authorization = Session.token;
}
return config || $q.when(config);
},
I expected this line to give me the "callee" path, but it actually gives me the requested one (angular has changed it before performing the request).
The route provider
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/admin', {
templateUrl: "/admin"
})
.otherwise({ redirectTo: '/' });
}])
This naughty little boy is changing my path prior to the request. I guess I can do something here on resolve, but I don't find this useful, since it will completely break encapsulation, forcing me to resolve each time I call any protected resource, so I can pass the current path to some service in my auth app.
So, how can I get the previous path from the interceptor, or some service within the auth app, without explicitly sending it from the route provider configuration?
Why don't you watch $on route change events and bind your current/previous paths to a $window or global $scope variable?
I haven't read all your code above, but this is how I handle location things within my app:
$scope.$on('$routeChangeSuccess', function(evt, current, previous){
var routeData = {};
//prop: uriSegment
//desc: get all uri segments for current location
routeData.uriSegment = [];
routeData.uriSegment = $location.path().substr(1).split('/');
//prop: urls
//desc: get current & previous urls
routeData.urls = {};
routeData.urls.curr = $location.url().substr(1);
//...previous url
if(previous && previous.originalPath){
//remove matching structure & slashes from url & add params as plain values
var prevUrl = previous.originalPath.substr(1).replace('/:', '/').replace(':', '');
if(previous.pathParams){
angular.forEach(previous.pathParams, function(val, key){
prevUrl = prevUrl.replace(key, val);
});
}
routeData.urls.prev = prevUrl;
}
//bind routeData to scope/window...
$scope.uriSegment = routeData.uriSegment;
$scope.urls = routeData.urls;
});
How does it work:
1. Get current & previous url:
$scope.urls.curr //shows current url
$scope.urls.prev //shows previous url
... if your route is defined like /admin/:userID and you are on that page, the url will contain that specific userID, it won't return the param name. /admin/53 will be /admin/53.
2. Get a specific url segment from current url:
$scope.uriSegment[1]
... for current url like /admin/53 will print 53
There were a lot more things in the above code, but I think that's what you need - to remember the previous url. You can play with route events and build your own.
I see that you inject $routeScope in your factories, so you can add my code in your main controller and update the $routeScope with routeData on every route change and then you can go like this:
...
previousLocation = $rootScope.urls.prev;
...
More about route events here.

Intercept route and modify it before ui-router responds in AngularJS

I'm using HTML5 pushstate on a mobile AngularJS website. When someone hits the desktop version, they get redirected to the mobile site. Sometimes the URL has a hash that the desktop site uses. The extra # is causing me digest loop problems.
I want to modify the route string before UI router even has a chance to act on it. Given:
http://www2.health.com.au/product/basic/#?blah I only want UI router to read the path /product/basic/.
I've tried adding a rule to the route provider (docs) that checks for '#?' and splits the string - but this seems to have no effect:
$urlRouterProvider.rule(function ($injector, $location){
var path = $location.url();
debugger;
if(path[path.length - 1].indexOf('#?')){
var splitpath = path.split('#?');
$location.replace().path(splitpath[0]);
return splitpath[0];
}
});
And the route ends up like this: http://www2.health.com.au/product/basic/#%252525252525252525252525252525252525252525252525252525252525252525252.......
With the error 10 $digest() iterations reached
I was under the impression that rules intercept the route, change it if possible and then pass the fixed route for ui-router to handle. Can anybody shed any light?
I am not particularly happy with this solution, but I ended up doing a manual bootstrap WITH A FREAKIN SETTIMEOUT!!! without it, angular.bootstrap() races the window.location = ... and wins unbelievably.
angular.element(document).ready(function(){
var loc = window.location.href,
index = loc.indexOf('#?');
if(index > 0){
alert('needsfix');
window.location = loc.substring(0, index);
setTimeout(function() {
angular.bootstrap(document, ['health3App']);
}, 500);
} else {
angular.bootstrap(document, ['health3App']);
}
})

How to multilingual url when user enter, copy paste or click url from other website

I have angularjs project implemented multi-language and using ui-router for routing. Every language will be have different url. Ex:
http://example.com/!#/en-us/english-title
http://example.com/!#/es-es/spanish-title
All state with url registered automatically when app run and load them from database. Ex:
angular.module('bxApp').run(["$http", function ($http) {
$http.get('/Home/Routes').success(function (result) {
result = result || {};
if (angular.isDefined(result) && result !== null) {
_.each(result.Routes, function (route) {
stateProvider.state(route.Name, {
url: route.Url,
templateUrl: route.TemplateUrl,
controller: route.Controller,
});
});
}
});
}]);
It work well but it will not work when user copy this link and paste to browser or click this link from other website . I think because of state can't found so it will be redirect to default and it does not keep url that user enter or copy.
In this case , How to do that?
Thanks,
You're declaring your states as a result of an HTTP call to your server: the problem is that these states are defined too late for the user to navigate to them when he pastes the URL in a new tab.
To understand, let's deconstruct what happens :
The user is on the initial page / other website, and copies the URL.
He pastes it in a new tab
Your angular application loads, finishes its config phase without having declared any of those states, and sends an HTTP call.
ui-router fails to route to a state matching the pasted URL, since the corresponding state is not here yet, and redirects to default
The HTTP response comes back, and your states are created (but too late).
How to make it work ?
My first reaction would simply not to store your states on your server. Unless you want the very core of your UX to be language-dependent, you don't have to do that.
But hey, let's say we want to do it anyway. I suggest you try this : declare a toplevel 'language' state, and have it load the other states in a resolve clause. This will 'block' the routing until the other states are declared :
angular.module('bxApp')
.config(['$urlRouterProvider', function ($urlRouterProvider) {
$urlRouterProvider
.state('language',{
url: '/:language',
resolve: {
childrenLoaded: ['$http', function ($http) {
// returning a promise is essential to have the 'waiting' behavior
return $http.get('/Home/Routes').then(function (data) {
var result = data.result || {};
if (angular.isDefined(result) && result !== null) {
_.each(result.Routes, function (route) {
$stateProvider.state(route.Name, {
url: route.Url,
templateUrl: route.TemplateUrl,
controller: route.Controller
});
});
}
});
}]
}
})
}]);
Again, this approach is probably asking for trouble : I strongly recommend you hardcode your states instead of storing them in a database. If all that varies from one language to another is the text and URL, then you will be fine with an URL param.

How not to change url when show 404 error page with ui-router

I want to show 404 error page, but also I want to save wrong url in location.
If I'll do something like that:
$urlRouterProvider.otherwise('404');
$stateProvider
.state('404', {
url: '/404',
template: error404Template
});
url will change to /404. How I can show error message on wrong urls without changing actual url?
There is solution for this scenario. We'd use 1) one ui-router native feature and 2) one configuration setting. A working example could be observed here.
1) A native feature of ui-router state definitions is:
The url is not needed. State could be defined without its representation in location.
2) A configuration setting is:
otherwise() for invalid routes, which parameter does not have to be a string with default url, but could be a function as well (cite):
path String | Function The url path you want to redirect to or a function rule that returns the url path. The function version is passed two params: $injector and $location
Solution: combination of these two. We would have state without url and custom otherwise:
$stateProvider
.state('404', {
// no url defined
template: '<div>error</div>',
})
$urlRouterProvider.otherwise(function($injector, $location){
var state = $injector.get('$state');
state.go('404');
return $location.path();
});
Check that all in this working example
As of ui-router#0.2.15 you can use $state.go() and send option not to update the location:
$urlRouterProvider.otherwise(function($injector) {
var $state = $injector.get('$state');
$state.go('404', null, {
location: false
});
});

Resources