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);
}
}
})
Related
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"} }
}
});
I'm developing a modular application with angularJS.
There is a menu that allows navigation through multiple views. (i'm using ngRoute)
Each view is divided in few parts and each part should load an independent module (lets call it "modules" atm). These modules will retrieve dynamic data using an api and these modules could be used in several views.
Which is the best way to do this? Use custom directives with a template and controller for each?
I would create the modules with a "module.(module #)" so you can separate all js files. Something like this since you are using ngroute:
MyApp.controller('module.one', function ($scope, $http, $routeParams, moduleOneResource) {...logic... }
Create a factory for each module:
angular.module('module.one').factory('moduleOneResource', ['$resource', function ($resource) {
return $resource('/api_root/module/:module_id', {} {
'save': {
method: 'POST',
headers: {"Content-Type": "application/json"},
'get': {
method: 'GET',
headers: {"Content-Type": "application/json"},
}
}
});
}]);
And the config for the module:
angular.module('module.one', []).config(['$routeProvider',
function($routeProvider) {
$routeProvider.when('/module/one/new', {templateUrl: 'partials/moduleOne/new.html', controller: 'ModuleOneCtrl'});
$routeProvider.when('/module/one/list_all', {templateUrl: 'partials/moduleOne/list.html', controller: 'ModuleOneCtrl'});
}]);
Then just keep creating each one of these files for each modules, should be 3 files per module... you can include more than one controller for example in one controller file if you want to for one module, same with factories.
angular.module('module.two', []).config([ .... config module for each module with url routes and html source , etc...
You could use ng-include for this but I would say its better to use ui-router instead of ngRoute. ui-router allows you to use multiple named views and nested views which could be what you want i think. For example
<body ng-app="myApp">
<div ui-view="header"></div>
<div ui-view="content"></div>
<div ui-view="footer"></div>
</body>
in config
var mypApp = angular.module("myApp",[ui-router]);
myApp.config(['$stateProvider', '$urlRouterProvider',
function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('empty', {
url:'/',
views: {
'header': {
templateUrl: 'apps/header.html',
controller: headController
},
'content': {
templateUrl: 'apps/content.html'.
controller: contentController
},
'footer': {
templateUrl : 'apps/footer.html',
controller: footerController
}
}
})
.state('test',{
url:'/test',
views: {
'header': {
templateUrl: 'apps/headertest.html'
controller: headtTestController
},
'content': {
templateUrl: 'apps/contenttest.html',
controller: contenTesttController
},
'footer': {
templateUrl : 'apps/footertest.html',
controller: footerTestController
}
}
})
}]);
This is a basic example as to how app is divided into multiple ui-views and you could devide your app similarly with each view having a controller.
I'm wanting to run some initialization code before my actual menu shows. That is, in my real app, I have an http resource I want to load that will effect what choices are shown in the menu choice list. More specifically, I want to put up a "waiting" message when my program first runs until the http resource completely loads. I've been experiminent with resolves but that seems to only help me after the menu has loaded.
I'm hoping not to have to use the bootstrap pattern since I want to do this in multiple places in my app, not just the first time it runs. I'm also not a big fan of the bootstrap pattern. It seems a little contrived to me.
I'm trying to do this with a very simple example first I have loaded here:
http://plnkr.co/edit/eehhA1CR2sEMQZrQBo3E?p=preview
var app = angular.module('svccApp', [
'ui.router'
]);
app.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider,$q,$timeout) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('home', {
url: '/',
template: '<p>HOME HERE</p>',
controller: 'HomeController as vm'
}).
state('about', {
url: '/about',
template: '<p>about here title: {{vm.title}}</p>', //'index4template.html',
controller: 'AboutController as vm',
resolve: {
title: function(){
return 'from title function';
}
}
});
}]);
var injectParamsAbout = ['title'];
var AboutController = function (title) {
var vm = this;
vm.title = title;
};
AboutController.$inject = injectParamsAbout;
angular.module('svccApp').controller('AboutController', AboutController);
var injectParamsHome = [];
var HomeController = function () {
};
HomeController.$inject = injectParamsHome;
angular.module('svccApp').controller('HomeController', HomeController);
What about using parent and child (not sure if this is not your not-liked bootstrap pattern). I created/updated working plunker here
// this is loaded immediately
// and that means, that user will see ...loading... ASAP
.state('about', {
url: '/about',
template: '<p ui-view="">...loading...</p>', //'index4template.html',
controller: function($state){ $state.go("about.child"); },
//controller: 'AboutController as vm',
})
// this state is triggered in the controller above
// and it will take 2,5 second to do all the resolve stuff
.state('about.child', {
template: '<p>about here title: {{vm.title}}</p>',
controller: 'AboutController as vm',
resolve: {
title: function($timeout){
return $timeout(function(){
return 'from title function';
}
, 2500 // wait 2,5 second - and then replace ...loading...
);
}
}
The idea behind is to load some template (e.g. ...loading...), as a parent view. Once instantiated (loading is displayed) we redirect to our child. In the example above, there is 2,5 second delay (loading form server), and then the child replaces parent fully...
Check it here
Based in #Radim example, a more clearer aproach that worked for me:
If you are using ui-router then you could resolve this using nested states. For example:
$stateProvider
.state("main", {
url: "/",
template: '<div ui-view></div>',
controller: 'InitController'
})
.state("main.landing", {
url: "landing",
templateUrl: "modules/home/views/landing.html",
controller: 'LandingPageController'
})
.state("main.profile", {
url: "profile",
templateUrl: "modules/home/views/profile.html",
controller: 'ProfileController'
});
In this example you have defined 3 routes: "/", "/landing", "/profile"
So, InitController (related to "/" route) gets called always, even if the user enters directly at /landing or /profile
Important: Don't forget to include <div ui-view></div> to enable the child states controller load on this section
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.
I want my app to fetch data from a server API, lets say I have the following API /orders , /users. Basically I just want to display the json I get from the server in a table. I am using ng-table directive for that purpose. So, in terms of components I have :
Services - both services do the same thing - go to an API and fetch JSON
Views - same view for both of the APIs, just display different data
Controllers - both fetch data from the service and display it in the table view.
So the way I see it, they all do the same thing with very minor adjustments. What I would like to do is
angular.module('admin').config(function ($routeProvider, $locationProvider) {
// same template and controller for both
$routeProvider.
when('/users', {
templateUrl: '/partials/table.html',
controllers: '/js/controllers/table.js
}).
when('/orders', {
templateUrl: '/partials/table.html',
controllers: '/js/controllers/table.js'
});
});
And in my service
factory('AdminService', ['$resource', function($resource) {
// somehow I want to inject the right endpoint, depending on the route
return $resource( '/:endpoint',
{ }, {} );
}]);
And in my table controller as well, I want to be able to know what to pass to the service
I could of course use separate controllers and services for each API endpoint it just seems like a wasteful duplication of code that does 99% the same thing
Is this possible ?
How do I wire everything together ?
If you want separate routes, but the same controller, but with some options, you can use the resolve option in the route definition to pass some options:
$routeProvider.
when('/users', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'option1': function() {
return 'val1'
},
'option2': function() {
return 'val2'
}
}
}).
when('/orders', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'option1': function() {
return 'val3'
},
'option2': function() {
return 'val4'
}
}
});
Then the controller in both cases will be injected with "option1" and "option2", which can be used to customise its behaviour:
app.controller('TableController', function($scope, option1, option2) {
// Do something with option1 or option1
});
From the resolve object functions, you could return a $resource object, or even return a promise that will be resolved with some data before the route is displayed. You can see the docs for $routeProvider for details.
Edit: For the resource, you could write a configurable factory like:
app.factory('MyResource', function($resource) {
return function(endpoint) {
return $resource('/' + endpoint);
}
});
And then use it in the controller:
app.controller('TableController', function($scope, MyResource, endpoint) {
var currentResource = MyResource(endpoint);
currentResource.query(); // Whatever you want to do with the $resource;
}
assuming that "endpoint" was was one of the options added in the resolve, so something like
when('/orders', {
templateUrl: '/partials/table.html',
controller: 'TableController',
resolve: {
'endpoint': function() {
return '/orders'
}