ui-router dynamic template path - angularjs

I'm using ui-router 0.2.8. I'm wanting to load a template based on device width. I can get the device width without issue, set it in the scope etc but I can figure out how to bind it to $stateParams. I have the scope variable in another controller which can be accessed the state's controller it's just not available to the state itself. I've tried the templateProvider but that is only returning me a string. What else can I try in order for this to work?
.state('list', {
abstract: true,
url: "/list",
templateUrl: 'views/'+$stateParams.somevalue+'/page.html',
controller: "myCtrl"
})
.state('list.first', {
url: "/first",
templateUrl: "views/first.html"
})

You might be looking for dynamic template name based on the state params
$stateProvider.state('contacts', {
templateUrl: function ($stateParams){
return '/partials/contacts.' + $stateParams.filterBy + '.html';
}
})
See the docs for more information
https://github.com/angular-ui/ui-router/wiki#templates

You can access to state params in the $stateChangeStart event. You can also dynamically update the templateUrl there as well.
So perhaps your code might look something like this:
angular.module('app', ['ui.router'])
.run(function($rootScope){
$rootScope.$on('$stateChangeStart', function(event, toState, toParams) {
if (toState.name === 'list') {
toState.templateUrl = 'views/'+toParams.somevalue+'/page.html';
}
});
}
You might also want to take a look at the onEnter callback supported by ui.router. I have not used this before but it might be neater than putting your template generating code into the $stateChangeStart event.

This reference here was great for me figuring out how to do dynamic templates in angular, but I'd like to update with my own solution.
Solution 1 based on Dipesh Kc's (this works great for redefining parent templateUrl for abstract states) Notice I used toParams instead of $stateParams:
// custom parent template
.state('template', {
abstract: true,
url: "/tm/:tmfolder/:tmview",
templateUrl: function (toParams) {
return 'views/' + toParams.tmfolder + '/' + toParams.tmview + '.html';
},
})
.state('template.contacts', {
url: "/contacts/:folder/:view",
templateUrl: function (toParams) {
return 'views/' + toParams.older + '/' + toParams.view + '.html';
},
})
Solution 2 based on biofractal's (there is no way to update a parent templateUrl using this method):
.state('contact', {
url: "/contact/:folder/:view"
})
$rootScope.$on('$stateChangeStart', function (event, toState, toParams) {
var customStates = ["contact"]
for (var i = 0; i < customStates.length; i++) {
if (toState.name.indexOf(customStates[i]) != -1) {
toState.templateUrl = 'views/' + toParams.folder + '/' + toParams.view + '.html';
break;
}
}
});

Related

Angular pass parameters in templateurl in stateprovider

I am using a $stateprovider as follows from angular-ui-router in my angular application:
.state('order', {
url: "/order/:id",
templateUrl: "/myapp/order"
})
In the above scenario, we are passing an id to the controller and we can call it as ui-sref="order({id: 1234})".
But now i want to make a direct call to the backend by not using a controller and pass the above as follows:
.state('order', {
url: "/order",
templateUrl: "/myapp/order/:id"
})
But obviously my syntax is wrong here.
How do i achieve this scenario?
I created working example. It is adjsuting thiw Q & A:
Trying to Dynamically set a templateUrl in controller based on constant
Let's have templates template-A.html and template-B.html in our example.
The state def with templateProvider would look like this
$stateProvider
.state('order', {
url: '/order/:id',
templateProvider: function($http, $templateCache, $stateParams) {
var id = $stateParams.id || 'A';
var templateName = 'template-' + id + '.html';
var tpl = $templateCache.get(templateName);
if (tpl) {
return tpl;
}
return $http
.get(templateName)
.then(function(response) {
tpl = response.data
$templateCache.put(templateName, tpl);
return tpl;
});
},
controller: function($state) {}
});
And now we can call it like this
<a href="#/order/A">
<a href="#/order/B">
Check it here
And what's more, with latest version of angularjs we can even make it super simple:
$templateRequest
Updated plunker
.state('order', {
url: '/order/:id',
templateProvider: function($templateRequest, $stateParams) {
var id = $stateParams.id || 'A';
var templateName = 'template-' + id + '.html';
return $templateRequest(templateName);
},
controller: function($state) {}
});

Requiring ui router stateparams and/or redirecting

I would like to know if anyone has come up with a way to require state params when using ui router with angular.js and/or how to redirect if param is not specified. I would prefer not to have to put controller code in the stateProvider config, but am open to any suggestions.
I've come close with some things like:
if (typeof $stateParams.myId == 'undefined') {
...
}
But I cannot put that in a resolve, so I am not sure where or how to check that. I have an abstract state with children and the abstract state have the stateparam....
.state('mystate', {
abstract: true,
url: '/state/:permalink',
template: '<div ui-view></div>'
})
.state('mystate.config', {
url: '/config',
templateUrl: 'partials/conf/config.html'
})
TIA
OK...
Thanks to levi... I have this partially working:
.state('conference', {
abstract: true,
url: '/conference/:permalink',
data: {hasPerma: true},
module: 'conf',
template: '<div ui-view></div>',
})
.state('conference.config', {
url: '/config',
templateUrl: 'partials/conf/config.html'
})
... this is allowing me to check to toState data, but the fromState module (or data) do not show up for some reason:
.run(['$state', '$stateParams', '$rootScope', function($state, $stateParams, $rootScope) {
$rootScope.$on('$stateChangeStart', function(e, toState, toParams, fromState, fromParams) {
console.log(toState.data.hasPerma); //working
console.log('from: ' + fromState.module); //not showing up
})
}])
So if I got to the base url and /conference/hello/config I can see the to info, but not the from ;-(

AngularJS - UI Router: Get template url from within the template

I'm creating states via ui-router, and attaching them to the same controller. (although obviously different instances of that controllers are initialized)
Inside of that controller, I'd like to know the name of the template it was initialized for.
My idea was to somehow pass to that controller properties in the stateProvider (ui-sref doesn't solve opening the browser with a deep link), but from my searches so far it appears it can't be done.
I can't simply check the name of the current state, since I'm working with multiple views.
I'm working with the same controller for multiple states and views, the controller can act accordingly as soon as he can know the name of the template he's attached to.
Here's how I create my states and views:
.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/home');
var controller = 'textFile';
var base = 'templates';
var views = ['home', 'about'];
for (var i = 0; i < views.length; i++)
{
var view = views[i];
$stateProvider.state(view, {
url: '/' + view,
views: {
'header': {
templateUrl: base + '/header.html',
controller: controller
},
'page': {
templateUrl: base + '/' + view + '.html',
controller: controller
},
'footer': {
templateUrl: base + '/footer.html',
controller: controller
}
}
});
}
}]);
I need to be able to know in textFile for instance if it's attached to header.html, footer.html, etc...
Why don't you use a resolve, so that you can access the unique data for each controller instance. For example:
$stateProvider.state(view, {
url: '/' + view,
views: {
'header': {
templateUrl: base + '/header.html',
controller: 'textFile',
resolve: {
viewName: function() { return 'header'; }
}
},
'page': {
templateUrl: base + '/' + view + '.html',
controller: 'textFile',
resolve: {
viewName: function() { return 'page'; }
}
},
'footer': {
templateUrl: base + '/footer.html',
controller: 'textFile',
resolve: {
viewName: function() { return 'footer'; }
}
}
}
});
Then you can access the value in your controller:
.controller('textFile', function($scope, viewName){
console.log(viewName);
});

angular-ui-router unable to create a dynamic named, named view

I am using angular-ui-router multiple views.
I have a constant module:
angular.module('app.constants', [])
.constant('constants', {
parent1: "parent1",
parent2: "parent2"
});
my state looks like this:
.config(['$stateProvider', 'constants', function ($stateProvider, constants) {
$stateProvider.state(constants.parent1, {
abstract: true,
url: '/' + constants.parent1
});
$stateProvider.state(constants.parent1 + ".state1", {
url: '/state1',
views: {
'cool-view#' + constants.parent1 + 'state1': {
templateUrl: root + 'views/splashScreen/splash.html',
controller: 'splashScreenCtrl'
}
}
});
$stateProvider.state(constants.parent2, {
abstract: true,
url: '/' + constants.parent2
});
$stateProvider.state(constants.parent2 + ".state1", {
url: '/state1',
views: {
'cool-view#' + constants.parent2 + 'state1': {
templateUrl: root + 'views/splashScreen/splash.html',
controller: 'splashScreenCtrl'
}
}
});
}]);
I have many parent abstract states.
In a child state i have a named view which i want to trigger.
The problem is that i can't dynamically create the line:
'cool-view#' + constants.parent1 + 'state1'
It will work with :
'cool-view#parent1.state1'
Gives me an error. How will i be able to create a dynamically field name inside the views object.
The reason i want to be able to create it dynamically is because i have a constant module which i might change in the future and i do not want to hardcode it in the views.
jsfiddle to solve this problem without much thinking about why you are doing this:
http://jsfiddle.net/qq1rbbb7/
var constants = { parent2: "someThing" };
var root = "root";
var views = {};
views['cool-view#' + constants.parent2 + 'state1'] = {
templateUrl: root + 'views/splashScreen/splash.html',
controller: 'splashScreenCtrl'
};
var state = {
'url': '/state1',
'views': views
};
console.debug(state);

How to do A/B testing with AngularJS templates?

I'm using ng-boilerplate and have to add the possibility to use different templates in production, based on the user configuration.
.config(function config( $stateProvider ) {
$stateProvider.state( 'demo', {
url: '/demo',
views: {
"main": {
controller: 'DemoCtrl',
templateUrl: 'demo/demo.tpl.html'
}
}
});
})
My current idea is to make the templateUrl dynamic
templateUrl: 'demo/demo'+userService.getTemplate()+'.tpl.html'
and having multiple template files, like:
demo.tpl.html (default)
demo.b.tpl.html (version b)
demo.c.tpl.html (version c)
while the userService function does provide the template version to use, e.g. ".b"
Do you agree? Is there maybe a better/easier approach to this problem?
AngularJS standard $routeProvider can accept function for templateUrl. But you can't inject services into this function.
ui-router has templateProvider parameter into which you can inject what you want and you should return something like this for remote template case:
$stateProvider.state('demo', {
templateProvider: function ($http, $templateCache, $stateParams, userService) {
var url = 'demo/demo' + userService.getTemplate() + '.tpl.html';
return $http.get(url, { cache: $templateCache }).then(function (response) {
return response.data;
});
}
})
I will not keep it in service, because service will be a part of js file. Which will be static (under normal condition)
This is how I will do it, In html file I will put
window.abConfig = "defaultVersion";
In app.js I will put
.config(function config( $stateProvider ) {
$stateProvider.state( 'demo', {
url: '/demo',
views: {
"main": {
controller: 'DemoCtrl',
templateUrl: function() {
return 'demo/demo' + window.abConfig + '.tpl.html';
}
}
}
});
})
Its kind of Hacky way, but it gives me flexibility to decide which version to display to user at server level. I might require to write logic before user download the content based on user's previous activity, which I can not do from client side javascript.
This can be achieved using standard angular, you just have to look at it from another angle!
I would suggest using the $templateCache. When you load the app you can pre-populate the $template cache with the selected version of the user templates.
You can do something like
$templateCache.put("page-header.html", '<h1>MyAwesomeStartup</h1><h2>Buy now!?!?!</h2>');
Also if your not opposed to the idea, you can place the templates into the page using the script tag syntax, where the id == the templateURL you use in your $routeProvider.
<script type="text/ng-template" id="page-header.html">
<h1>MyAwesomeStartup</h1><h2>Buy now!?!?!</h2>
</script>
And ng will load it directly from the script tag.
I've a different way based on the same principle
Except you don't have to actually request the view yourself with $http.
So you can let ui-router handle that part.
Which is easier when you have a complex view architecture.
.state('public.index', {
url: '/',
views: {
"": {
template: '<div ui-view="abTestDummyView"></div>',
controller: ['landing', '$http', function(landing, $http) {
alert('Showing AB Test Landing #' + landing);
// increment landing stats
$http.get('http://stats.domain.com', {landing: landing});
}],
controllerAs: 'landingCtrl',
},
"abTestDummyView#public.index": {
templateProvider: ['landing', function(landing) {
// inject a view based on its name
return "<div ui-view=\"ab" + landing + "\"></div>";
}]
},
"ab1#public.index": {
template: "INJECTED AB1"
// replace by templateUrl: "/real path/"
},
"ab2#public.index": {
template: "INJECTED AB2"
// replace by templateUrl: "/real path/"
}
},
resolve: {
landing: function() {
return Math.floor((Math.random() * 2) + 1);
}
}
})

Resources