I'm trying to create a BaseController which my controllers can extend to handle the history action for all models. Currently (for the other routes), I set it up like so:
$routeProvider.when('/', {
redirectTo: '/static/campaigns'
}).when('/static/campaigns/new', {
templateUrl: 'campaigns/new.html',
controller: 'CampaignNewController'
}).....
What I want to do for the history routes would be a general route which would cover all models, but I'm not sure if there's a way to use the route parameters within the setup like below.
when('/static/history/:model_name', {
templateUrl: model_name + '/history.html',
controller: model_name + 'Controller'
})
If you look at documentation :
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider#when
you may notice that, templateurl can be a string or a function. Perhaps if you created a function on the right hand side of templateUrl and injected $routeParams and read the value of model_name and returned a string with model_name, it might just work.
You wont be able to do the same for controller; however, you will have to create a service, that takes model_name as input and does the same logic as multiple controllers would.
Related
I'm using the native AngularJS router and was wondering if there is a way to assign a controller to a route conditionally. For example, let's say I have three user types:
Guest
User
System Admin
When they come to the home page, I want to be able assign a different controller based on the user type. So I would have three registered controllers: guestHomeCtrl, userHomeCtrl, systemAdminHomeCtrl.
I imagined something like this:
$routeProvider
.when( '/' , {
controller: getHomeCtrl(),
controllerAs: 'homeCtrl'
})
I know I can just pass in the string of the registered controller, but the main issue is being able to find out what type of user is logged in. I have a userService that typically keeps track of that, but it doesn't seem like I can access it from where I set up the routes. Or am I mistaken?
Any direction would help out a lot.
I would suggest using userService with a single controller. Read through the page below, specifically the resolve argument.
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
This blog post also describes how to use the resolve.
http://odetocode.com/blogs/scott/archive/2014/05/20/using-resolve-in-angularjs-routes.aspx
Try sending the specific function or variable you need to the controller. I used userService.User.userAccountStatus, but it really depends on the setup in your service file.
$routeProvider
.when( '/' , {
controller: HomeCtrl,
controllerAs: 'HomeCtrl',
resolve: {
userService: function(userService) {
return userService.User.userAccountStatus();
}
}
})
I use ngInject for injecting dependencies, but I assume you get the gist of giving services to a controller.
angular
.module('app')
.controller('HomeCtrl', HomeCtrl)
/** #ngInject */
function HomeCtrl($scope, $log, $state, userService) {
}
I can provide further examples if you need them.
I am trying to have dynamic routing in Angular 1.3. Something similar to what it described here and here. The examples suggest something like this:
$routeProvider.when('/:group/:pagename', {
controller: 'RouteCtrl',
templateUrl: 'uirouter.html'
})
and then the controller will have access to group and pagename through $routeParams. That works
I am trying to make this routing a little more dynamic and have template and controller to be selected dynamically as well:
$routeProvider.when('/:group/:pagename', {
controller: $routeParams.group + 'Ctrl',
templateUrl: $routeParams.pagename + '.html'
})
When I put a breakpoint on when I can see that there is $get property with a function that has $routeParams as one of parameters. But I can't figure out how to retrieve its values.
The project is in very early stage - I can go with ui-router or with ng-router if any of them has this functionality.
For the dynamic templateUrl portion you could try:
$routeProvider.when('/:group/:pagename', {
controller: "SomeCtrl",
templateUrl: function(params){return params.pagename + '.html';}
})
Not sure if the same could be done with the controller however.
Instead of a direct value you can declare a function that will return a templateUrl string, i.e.:
$routeProvider.when('/:group/:pagename', {
controller: $routeParams.group + 'Ctrl',
templateUrl: function (routeParams) {
return routeParams.pagename + '.html';
}
});
I guess the same may be true for controller, but you have to test that one out, as I've never used it for controllers.
That being said, if you have so much dynamic logic in this place, maybe instead of treating this as different controllers, you could encapsulate those views as different directives and the ng-if certain directive depending on $routeParams which will be set in one wrapping controller?
I have a large angular app that lives at a URL like so:
http://myangularapp.com/app/index.html#/
The URL I intend on giving out will need a name injected into the URL as this one site will support multiple users.
So, without modifying any directory structure, I'd like to do:
http://myangularapp.com/app/bob/index.html#/
and also:
http://myangularapp.com/app/harry/index.html#/
All my controllers and functionality would ideally stay the same.
Super stumped on this one!
Use the $routeProvider service. You can define where urls go, like so.
$routeProvider.
when('/app/foo/bar/what/about/bob', {
templateUrl: 'app/bob/index.html', <---directory and url don't have to match
controller: 'bobController'
}).
when('/app/harry', { ...
})
Let's say I have this config:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'app/partials/index.html',
controller: 'defaultCtrl'
})
.when('/other', {
templateUrl: 'app/partials/other.html',
controller: 'errorCtrl'
})
.otherwise({
templateUrl: 'app/partials/404.html'
});
}
]);
I'm looking for a place to do some basic, common maintenance code before the router calls the route. Say I'd like to clear the console log via console.clear() every time the route is changed. How and where would be the best place in the code to do that?
The $route service raise events like $routeChangeStart which you can use to perform such tasks. You can implement them using the $scope.$on method. Someting like
$scope.$on('$routeChangeStart',function(angularEvent,next,current) {
//do you work here
});
Read the $route documentation to get idea on this and other such events.
Also $routeProvider configuration method when also can take a parameter resolve which actually is used to setup dependencies before the route is resolved. This resolve object map can also be used to achieve what you want to do.
I'm trying to structure my app using the Restful/Ruby convension /<resource>/[method]/[id]. How I've done it previously when using a server-side MVC framework like CodeIgniter was to dynamically route based on the URI:
ex.
www.foo.com/bar/baz/1
The app would then use method baz in controller/class bar and return views/bar/baz.php (populated with data from bar->baz)
I would like to do the same in Angular, but I'm not sure if it supports this (and if it does, I'm not sure exactly how to go about it). At the moment I'm using $routeProvider's when method to specify each case. $location.path() looks like it might have what I need, but I don't think I can use it in app.js (within config()).
What I'd like to do is something like this:
.config([
'$routeProvider', function($routeProvider) {
$routeProvider
.when(//<resource> controller exists
resource+'/'+method, {
"templateURL": "views/" + resource + "/" + method + ".html",
"controller": resource
}
).otherwise({ "redirectTo":"/error" });
}
]);
And the router automatically calls the appropriate method.
EDIT Also, why does $routeProvider freak out when I specify when('/foo/bar', {…}) ?
EDIT 2 Per Lee's suggestion, I'm looking into doing something like this:
$routeProvider
.when(
'/:resource/:method/:id', {
"templateUrl": function(routeParams){
var path = 'views/'+routeParams.resource+'/';
return ( typeof routeParams.method === 'undefined' ) ?
path+'index.html' : path+routeParams.method+'.html';
},
"controller": RESOURCE
})
.otherwise({redirectTo: '/error'});
I noticed the following in $routeProvider's doc:
templateUrl – {string=|function()=} – path or function that returns a
path to an html template that should be used by ngView.
If templateUrl is a function, it will be called with the following
parameters:
• {Array.<Object>} - route parameters extracted from the current
$location.path() by applying the current route
Edit: The option to set templateUrl to a function is part of the unstable 1.1.2 build: #1963 (but it doesn't work as of 2013-02-07).
There is a dicussion about adding this functionality on AngularJS's Github: #1193 #1524, but I can't tell if it was actually implemented (in the docs from Dash quoted above, it looks like it has been, and the docs on the site haven't been updated yet).
EDIT 3 To clarify what I want to happen (per lee's request), in simplest terms, I would like to go to www.foo.com/index.html#/people
Angular should use controller people, automatically call its index method, and should serve up
./views/people/index.html
./views/people/map.html
Also, if I go to www.foo.com/index.html#/people/map
Angular should use the people controller again, but this time automcatically call its map method and serve up …map.html (because map was specified in the url)
./views/people/index.html
./views/people/map.html
Then, if I go to
www.foo.com/index.html#/widgets
Angular should serve up
./views/widgets/index.html
./views/widgets/details.html
The code for the router should be very generic—I shouldn't have to specify a .when() for every route.
Thinking about this a little more. You could just have a single controller for those generic CRUD/REST type operations. Then load the templates using the resource and view parameters.
Create
#/foo/create/0
This has it's own form template "/views/foo/create.html" and the 0 os just there for a placeholder.
on submit you would call a method on the controller ng-click="save()" which would post to the server at POST "/rest/foo".
Read
#/foo/view/1
Again the template "/views/foo/view.html" is just a view of the data
You can call a service method to get the data from your server using GET "/rest/foo/1"
Update
-#/foo/edit/1
Could use the same template as create or you could use a different one "/views/foo/edit.html" if you like.
Also pull the data using GET "/rest/foo/1"
Submit the data using PUT "/rest/foo/1"
Delete
#/foo/delete/1
service method would call DELETE "/rest/foo/1"
I don't think you want a hash for this, but you could use one because the controller could actually do a verification or anything you like to confirm the deletion. Maybe have a view called "/views/foo/delete.html" that asks if you want to delete the record. Then you could have ng-click="delete(itemid)" on a button somewhere that deletes the item via ajax.
All this could be done using a single controller/service and dynamically generating the service and view urls.
Anything that's custom you would need a custom controller and custom routes and service methods for. I could probably throw together an example, but not tonight.
Here is a project on github that does something close to what you are asking
EDIT:
I discovered something interesting that had not occurred to me before. If you leave out the controller in the route it will use the controller specified in the template. So as long as all the templates that you use for a given controller have ng-controller="resource" then it will load that controller for the template as expected. Of course with the current implementation of routes there are no optional parameters, so if you have two or three parameters you would need to specify a separate route. Biggest problem is it appears to call the controller method twice. I am guessing this is because there are two views with the same controller. However one view should replace the other so there should not be two calls. This seems like a bug to me. I also found some discussion of a possible new routing system in the works that may meet your needs, but it may be pretty far off: https://github.com/angular-ui/router/issues?page=1&state=open. The sample on github is now using the following method so you can browse that if you like.
var restrouteApp = angular.module('restrouteApp', [])
.config(['$routeProvider', function($routeProvider) {
$routeProvider
.when('/:ctrl/:method', {
templateUrl: function(rp){
if(!rp.method) {rp.method = 'index';}
console.log('route one');
return 'views/'+rp.ctrl+'/'+rp.method+'.html';
}
})
.when('/:ctrl/:method/:id', {
templateUrl: function(rp){
if(!rp.method) {rp.method = 'index';}
console.log('route two');
return 'views/'+rp.ctrl+'/'+rp.method+'.html';
}
})
.otherwise({
redirectTo: '/resource1/'
});
}]);
And the templates:
<div ng-controller="resource1">
<h1> resource1/one.html </h1>
<div>{{r1data.selected}}</div>
</div>
Now in your controller you can do this to call the method dynamically.
restrouteApp.controller('resource1', function($scope,$routeParams,$log,Resource1Service) {
$log.info('new resource1');
$scope.controllername = $routeParams.ctrl;
$scope.r1data= Resource1Service.shared;
$scope.index = function(){
Resource1Service.index().then(function(){
//when the service returns
});
}
$scope.one = function(){
$scope.r1data.selected = $scope.r1data.resources[0];
}
$scope.two= function(){
$scope.r1data.selected = $scope.r1data.resources[1];
}
//call the specified method of this controller
$scope[$routeParams.method]();
});
/EDIT
To conform to existing routing systems like Rails, the ability to define the method in the route is now available.
I created a super simple solution that allows routes to call a method based on the route definition and a directive in the view. I think ui-router is not conventional and is too complicated for a such a "should be" core feature.
The project is called ngMethod and is located at: https://github.com/jzumbrun/ng-method.
An example of its use is: https://github.com/jzumbrun/chrome-apps-angularjs-bootstrap
So if I have a route like so:
$routeProvider.
when('/contacts/new', {
controller: 'ContactsController',
method: 'new',
templateUrl: $configProvider.template('contacts/form.html'),
});
$routeProvider.
when('/contacts/:id/edit', {
controller: 'ContactsController',
method: 'edit',
templateUrl: $configProvider.template('contacts/form.html'),
});
and I have ng-method in the contacts/form template:
<div class="col-lg-12" ng-method>
<form role="form">
...
Then the ng-method will call either $scope.edit() or $scope.new() in the ContactsController.
Than the contacts/form template can be shared, and depending on the route call the correct method
to load the data. This style is now more "Angularjs" and the loading the code is much like angular calling to modules and controllers.
The full directive that makes this happen is less than 20 lines of code:
app.directive('ngMethod', ['$route', function($route) {
return {
// Restrict it to be an attribute in this case
restrict: 'A',
// responsible for registering DOM listeners as well as updating the DOM
link: function(scope, element, attrs) {
// Call method without params. Use $routeParams
if(angular.isFunction(scope[attrs.ngMethod])){
scope[attrs.ngMethod]();
// default to the route method if attrs.ngMethod is empty
} else if(angular.isObject($route.current)
&& angular.isString($route.current['method'])
&& angular.isFunction(scope[$route.current['method']])){
scope[$route.current['method']]();
}
}
};
}]);
This is now possible with ui-router 0.2.8:
$stateProvider
.state('base', {
url: '/:resource/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming app.controller('FooCtrl',[…])
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
});
But in order to take advantage of $state.includes() on nav menus, this would probably be better:
$stateProvider
.state('base.RESOURCE_NAME1', {
url: '/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming the convention FooCtrl
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
}).state('base.RESOURCE_NAME2', {
url: '/:collection/:id',
controllerProvider: function( $stateParams )
{ // assuming the convention FooCtrl
return $stateParams.collection + 'Ctrl';
},
templateUrl: function( $stateParams )
{
return '/partials/' + $stateParams.collection + '.html';
}
});
The above could be simplified with a loop to build the states from an array of resources ($stateProvider supports adding states basically whenever):
var resources = [ 'r1', 'r2', '…' ];
for ( var r = resources.length-1; r >=0; r-- )
{
var name = resources[r];
$stateProvider.state('base.'+name, {
…
});
}
Caveat ui-router doesn't not really support optional state parameters (planned for v0.4)