Dynamic template URL - angularjs

I am diving head first into Angular 1 for the first time with an existing app. One of the things about this app that I want to change is how there are separate services and other things for every entity in our application.
I've abstracted it out so that there's only one service for all entities, but now I am trying to load a template where the file name is equal to a particular state parameter.
Here's how the per-entity routing is done now:
namespace App.Employee {
'use strict';
angular
.module('app.employee')
.run(appRun);
appRun.$inject = ['routerHelper'];
function appRun(routerHelper: Common.Router.IRouterHelperService) {
routerHelper.configureStates(getStates());
}
function getStates() {
return [{
name: 'employee',
url: '/employee/{employeeId}',
templateUrl: 'app/employee/employee.html',
controller: 'employeeCtrl',
controllerAs: 'vm',
data: {
title: 'Employee'
}
}];
}
}
Here's what I want to change it to:
namespace App.Entity {
'use strict';
angular
.module('app.entity')
.run(appRun);
appRun.$inject = ['routerHelper'];
function appRun(routerHelper: Common.Router.IRouterHelperService) {
routerHelper.configureStates(getStates());
}
function getStates() {
return [{
name: '||entityTypeName||',
url: '/{entityTypeName}/{entityId}',
templateUrl: 'app/entity/||entityTypeName||.html',
controller: 'entityCtrl',
controllerAs: 'vm',
data: {
title: '||entityTypeName||'
}
}];
}
}
Notice how I introduced {entityTypeName} in the URL. This successfully points to the proper Web API service and pulls back the entity. However, I want to tokenize the ||entityTypeName|| placeholder to be what's matched for {entityTypeName}. That's the general idea, at least.
I know little of Angular at this point and am learning as I go along, so let me know if additional code is needed.

The templateUrl property can be given a function instead of a string, and it will be passed the current state parameters. Like so:
templateUrl: function(params) {
return 'app/entity/' + params.entityTypeName + '.html';
}
The params argument is provided for you by the ui-router framework.

I also do something similar using dynamic routing with views and view parameters like so:
/**
* url: "/view?a"
* Will match to url of "/view?a=value"
*/
.state('root.view', {
url: '/view?a',
views: {
'main-content#': { templateUrl: function(params) {console.log(params.a); return 'app/views/ + params.path + ".php"} }
}
})
Or for the following:
/**
* Dynamic Routing
*/
.state('root.catpath', {
url: '/{path:.*}',
views: {
'main-content#': { templateUrl: function(params) {console.log(params); return 'app/views/' + params.path + ".php"} }
}
});

Related

Why im getting 404 error when i using .html for state?

I have this states: first one in partial view and second one is html page and they are in folder View/Account.First one is working but secondone is not.It say 404 page could not be found. How can i configure state for .html page.Any suggestion?
.state('account.payoutconfirmation', {
url: '/ticketprint',
templateUrl:
function (stateParams) {
return mainTemplateService.getTemplateUrl(stateParams, '/account/payoutconfirmation');
}
}).state('account.state', {
url: '/printticket',
templateUrl:
function (stateParams) {
return mainTemplateService.getTemplateUrl(stateParams, "/account/state.html")
}
I would say, there could be some typo. Anyhow, in case we are using some mainTemplateService to load the url, we should not use templateUrl, but templateProvider.
There is a working plunker
This could be the service:
.factory('mainTemplateService', ['$templateRequest', function($templateRequest) {
return {
getTemplateUrl: function(stateParams, urlPart) {
var url = "temp" + urlPart;
return $templateRequest(url);
}
}
}])
And these are states:
.state('account.payoutconfirmation', {
url: '/ticketprint',
templateProvider: ['$stateParams', 'mainTemplateService',
function(stateParams, mainTemplateService) {
return mainTemplateService.getTemplateUrl(stateParams, '/account/payoutconfirmation');
}],
})
.state('account.state', {
url: '/printticket',
templateProvider: ['$stateParams', 'mainTemplateService',
function(stateParams, mainTemplateService) {
return mainTemplateService.getTemplateUrl(stateParams, "/account/state.html")
}],
})
Check it here
Also, check these:
Angular and UI-Router, how to set a dynamic templateUrl
Trying to Dynamically set a templateUrl in controller based on constant

angularjs workout required parameters for any state

I an trying to list out all the URL's that exist in the stateprovider (angularjs 1.2.26).
given the example below, (very much cut down state list):
angular.module('app')
.config(function ($stateProvider) {
$stateProvider
.state('app.vendors', {
url: '/vendors',
templateUrl: 'app/vendor/list.html',
controller: 'Vendor.ListController as vm',
})
.state('app.vendor', {
url: '/vendor/{vendorId}',
templateUrl: 'app/vendor/details.html',
controller: 'Vendor.DetailsController as vm',
data: {
subnav: [
{ title: 'Details', icon: 'fa-align-left', state: 'app.vendor', permissions: 'get-vendor', exactStateOnly: true },
{ title: 'Sites', icon: 'fa-archive', state: 'app.vendor.sites', permissions: 'get-site' },
{ title: 'NCRs', icon: 'fa-copy', state: 'app.vendor.ncrs', permissions: 'get-vendor' }
],
requiredPermissions: ['get-vendor']
}
})
.state('app.vendor.sites', {
url: '/sites',
templateUrl: 'app/vendor/site/list.html',
controller: 'Vendor.Site.ListController as vm',
data: {
requiredPermissions: ['get-site']
}
})
.state('app.vendor.site', {
url: '/site/{siteId}',
templateUrl: 'app/vendor/site/details.html',
controller: 'Vendor.Site.DetailsController as vm',
data: {
requiredPermissions: ['get-site']
}
})
.state('app.vendor.ncrs', {
url: '/ncrs',
templateUrl: 'app/vendor/ncr/ncrList.html',
controller: 'Vendor.NCR.NCRListController as vm',
data: {
requiredPermissions: ['get-vendor']
}
});
});
to get to a particular vendor you would use state:
app.vendor({vendorId: 1})
to get to its site
app.vendor.site({vendorId: 1, siteId: 2})
if I pass in the $state object to a controller I can list all the states with state.get().
If I list them the urls only contain the last part (i.e. what is in the config, and relative to its parent). I can use $state.href('app.vendor.site') which will give me almost the whole url, but misses out the parameters. I am trying to find a way at runtime to know what or at least how many parameters it requires.
My goal is to try and create a basic smoke test for every page in our Angular app to ensure it loads something and doesn't through errors in the console. I dont want to have to manually maintain a list of urls with params. (all our params are int IDs so I can simply use "1" in the params to test the url).
The private portion of the state contains params and ownParams objects. You can use a decorator to access those internal variables. See my previous answer regarding exposing the entire internal state object using a decorator: UI-Router $state.$current wrapper for arbitary state
After decorating your state objects, use the $$state() function to retrieve the private portion. Then query the state for its params and generate the href.
angular.forEach($state.get(), function(state) {
var paramKeys = state.$$state().params.$$keys();
var fakeStateParams = {};
angular.forEach(paramKeys, function(key) { fakeStateParams[key] = key; });
console.log($state.href(state, fakeStateParams));
});

One controller to another scopes and sharing in Angular

Angular started off easy to understand but today I'm completely lost and would appreciated any help that is offered here for this problem I've come across.
So I have a two column page, one side the filters and the other the list of things to be filtered. This page is created using the bit of html injected in to the DOM. I have a filter template and a list template and they both have their own controllers (filterCtrl and filterCtrl).
I understood that I needed to seperate the http request for the controllers in to factories, so at the moment an example factory and controller in my app look like this.
fpApp.controller('listController', ['$scope', 'eventsFactory',
function($scope, eventsFactory) {
eventsFactory.eventList().then(
function(data){
$scope.events = data;
}
);
}
]);
and
fpApp.factory('eventsFactory', function ( $http ) {
return {
eventList: function() {
return $http.get('/js/db/events.json')
.then(
function(result) {
return result.data;
});
}
};
});
at the moment every thing is grand, I have a interface built up from partials and everything is outputting what I need. My problem is that I need to apply those filters to the list but have no idea how to get the information out of one controller in to the other controller or even one template to the other.. Once I'm able to get data from one to the other ill be good.
fpApp.config(function($stateProvider, $urlRouterProvider) {
//
// For any unmatched url, redirect to /state1
$urlRouterProvider.otherwise("/grid");
//
// Now set up the states
$stateProvider
.state('grid', {
url: '/grid',
views: {
// the main template will be placed here (relatively named)
'': { templateUrl: '/templates/grid.html' },
'filerList#grid': {
templateUrl: '/templates/partials/filterList.html',
controller: 'filterCtrl'
},
'viewSwitch#grid': {
templateUrl: '/templates/partials/viewSwitch.html'
},
'eventList#grid': {
templateUrl: '/templates/partials/eventList.html',
controller: 'listController'
}
}
})
.state('list', {
url: '/list',
views: {
// the main template will be placed here (relatively named)
'': { templateUrl: '/templates/list.html' },
'filerList#list': {
templateUrl: '/templates/partials/filterList.html',
controller: 'filterCtrl'
},
'viewSwitch#list': {
templateUrl: '/templates/partials/viewSwitch.html'
},
'eventList#list': {
templateUrl: '/templates/partials/eventList.html',
controller: 'listController'
}
}
});
});
So in summary how do I affect the list controller when the user is clicking filters in the filterController?
Thank you for your time in advance.

dynamic template based on $routeProvider resolves

I've got a url in my application that needs to load one of two templates based on the results of a resolve call, like so:
app.config(function($routeProvider) {
$routeProvider.
when('/someurl', {
resolve: {
someData: function(dataService) {
var data = dataService.loadData();
// data has a .type field that determines which template should be loaded
return data;
}
},
templateUrl: function(routeParams) {
// return a path based on the value of data.type in the someData resolve block
}
})
});
Is there a way for me to set the templateUrl based on what's returned by the someData resolve?
So I figured out how to do this using ui-router - thanks m59 for pointing me at it.
Here's how you'd do this with ui-router:
app.config(function($stateProvider) {
$stateProvider
.state('someState'
url: '/someurl',
template: '<ui-view>',
resolve: {
someData: function(dataService) {
var data = dataService.loadData();
// data has a .type field that determines which template should be loaded
return data;
}
},
controller: function($state, someData) {
$state.go('someState.' + someData.type);
}
})
.state('someState.type1', {
templateUrl: 'someTemplate1.html',
controller: 'Type1Controller'
})
.state('someState.type2', {
templateUrl: 'someTemplate2.html',
controller: 'Type2Controller'
})
});
There's a parent state that handles resolves, then redirects to the child states based on the information it gets. The child states don't have a url, so they can't be loaded directly.

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