Is there a way of getting the URL attached to a controller by the $routeProvider other than hard-coding the URL in the href attribute? If you modify the routes in the $routeProvider you have to modify all the hard-coded URLs in the templates which is not very efficient.
For example the Django Framework provides the "reverse()" utility function and "url" template tag which given the "controller" (view function) and the URL parameters it returns the associated URL.
There is no built in way to do this but you can use angular.named-routes which provides reverse urls.
$routeProvider:
$routeProvider.when("/product/:id/:slug", {
templateUrl: "/product.html",
controller: "ProductController",
name: "product-detail"
});
$locationProvider.html5Mode(true);
$scope
{product: {id: 1, name: "Awesome Product", slug: "awesome-product"}}
Doing a reverse from template:
<a data-named-route="product-detail" data-kwarg-id="{{ product.id }}" data-kwarg-slug="{{ product.slug}}">{{ product.name }}</a>
Should turn into:
Awesome Product
Yes, but there is no built-in way to do this. Because even if a controller may be bound to the $routeProvider, it doesn't has to. A way to achieve it is to make all your Controllers inherit from a BaseController. This BaseCtrl inject into the scope the property "currentUrl".
myApp.controller("BaseCtrl" ,function ($scope, $location) {
});
myApp.controller("AnyCtrl" ,function ($scope, $location) {
//inherit from base
$injector.invoke(function ($controller) { $controller('BaseCtrl', {$scope: $scope}); });
});
Another way to achieve it is to "pollute" (thus not a very good idea but will work) the $rootScope by changing $rootScope.currentUrl each time the route has changed. Because all scopes inherit from $rootScope, the variable $scope.currentUrl will always be available and up to date into any view you want.
Related
I have the following usecase:
I have two views which are identical, as well as two controllers which are very similar. I intend to extend one controller with the other and just override the diff. The problem I am having is in ui-router I want to choose a controller by name (Since the templates is shared, and the controllers differ this cannot be declared in the template itself)
Before this refactoring I had the following code:
.state('edit-page-menu', {
url: '/sites/:siteId/page_menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: ['$scope', '$stateParams', (scope, stateParams) => {
scope.siteId = stateParams.siteId
scope.menuId = stateParams.menuId
}]
})
Which adds the stateParams to the scope. In this usecase I had the controller defined in the template and could modify the scope that way. However now when I switch to controller by name approach
.state('edit-page-menu', {
url: '/sites/:siteId/menus/:menuId',
templateUrl: 'partials/edit-menu.html',
controller: 'editMenuCtrl'
})
I don't know how to inject / add the $stateParams to the controller. And I would really like to avoid injecting "$state" in the controller and fetch the parameters that way. Is there a way to modify the scope of a controller if you choose it by name?
Best regards.
Suppose I have a general purpose controller, TableController, that can be used in multiple places in the app to display a table of Key x Value pairs via a custom directive, ui-table, that generates an HTML table.
angular.module('ui.table', [])
.controller('TableController', ['$scope', 'data',
function($scope, data) { $scope.data = data; }])
.directive('uiTable', function() {
return { templateUrl: 'table.html' }
});
I could use the controller in the following template:
<div ng:controller="TableController">
<div ui-table></div>
</div>
And create a factory to pass data to this controller.
.factory('data', function() {
return [{'App':'Demo', 'Version':'0.0.1'}];
})
But, I have multiple controllers (sometimes in the same views), so I need to "bind" a particular factory to a particular controller (e.g., UserProfile, AppData, etc.)
I have started to look at angular-ui-router's $stateProvider, but it seems too complicated for what must be a typical use case? What I'd really like to be able to do is use the template to annotate which factory (what I think of as a model) should be used for that particular controller. E.g., something like:
<div ng:controller="TableController" ng:model="AppData"></div>
What is the right approach?
EDIT:
I've figured out $stateProvider and "resolve" allows me to map provider services onto injected values for the state's main controller -- but the controller I want to influence is a child of this controller.
$stateProvider
.state('home', {
url: '/home',
templateUrl: '/home/view.html',
controller: 'MainViewController',
resolve: {
'data': 'AppData'
}
});
So, I still can't figure out how to influence the controllers inside the state's view.
I think what you are looking for is simply passing your data into the directive through attributes. Then use an isolated scope in directive so you can have multiple instances active at the same time
<div ng-controller="ViewController">
<div ui-table dataSource="tableData"></div>
</div>
Then your directive would be written in a generic way to be re-usable regardless of the data passed in.
.factory('SomeService', function(){
var data ={
headings: ['ID','Age','Name'],
rows:[
{id:1, 33,'Bill'}......
]
};
return {
get:function(){ return data;}
};
})
.controller('ViewController', function($scope, SomeService){
$scope.tableData = SomeService.get();
})
.directive.directive('uiTable', function () {
return {
scope: {
dataSource: '=' // isolated scope, 2 way binding
}
templateUrl: 'table.html',
controller: 'TableController', // this controller can be injected in children directives using `require`
}
});
In essence this is just reversing your layout of controller/directive. Instead of TableController wrapping the directive, it is used internally within directive. The only reason it is a controller in the directive is to allow it to be require'd by child directives such as perhaps row directive or headings directive and even cell directive. Otherwise if not needing to expose it for injection you can use link and put all sorts of table specific operations in there
As mentioned in my comments there are various approaches to creating a table directive. One is with heavy configuration objects, the other is with a lots of declarative view html that use many child directives. I would suggest analyzing the source of several different grid/table modules to see what best suits your coding style
Thanks in part to #charlietfl (above) I have an answer:
<ui-graph model="SomeGraphModel"></ui-graph>
Then:
angular.module('ui.graph', [])
.directive('uiGraph', [function() {
return {
controller: 'GraphController',
scope: {
model: '#model' // Bind the element's attribute to the scope.
}
}
}]);
.controller('GraphController', ['$injector', '$scope', '$element',
function($injector, $scope, $element) {
// Use the directive's attribute to inject the model.
var model = $scope.model && $injector.get($scope.model);
$scope.graph = new GraphControl($element).setModel(model);
}])
Then somewhere else in the app (i.e., not necessarily in the directive/controller's module):
angular.module('main', [])
.factory('SomeGraphModel', function() {
return new GraphModel();
})
I am using the Angular $routeProvider service to wire-up my single-page HTML5 applciation. I am using the following routing configuration:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController'
});
}]);
Within the ShowOrdersController I need access to the RESTful URL parameter described above as :orderId. It is suggested that to best achieve this, I should use the $routeParams service in my controller:
app.controller('ShowOrderController', function($scope, $routeParams) {
$scope.order_id = $routeParams.orderId;
});
I have serious concerns about this. My routing logic has now bled through to my controller! If I want to drastically change the routing scheme, I would have to go through all my controller code and correct all the references to $routeParams.
Furthermore, if I want to re-use the ShowOrderController for multiple routes, it's going to enforce all of the routes to use the same token variable :orderId.
This just seems like poor coding to me. It would make more sense to provide some linking mechanism, so the router can specify well-known parameters to the controller.
This would be just like how a modal's resolve method works:
$modal.open({
controller: 'ShowOrderController',
resolve: {
orderId: function () {
return $routeParams.orderId;
}
}
});
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
Is there any way to achieve this or something similar with the out-of-the-box AngularJS routing services?
As per AngularJS - How to pass up to date $routeParams to resolve? it is possible to reference the current route's parameters in the resolve method of the $routeProvider using $route.current.params:
app.config(['$routeProvider',
function($routeProvider) {
$routeProvider.
when('/show-order/:orderId', {
templateUrl: 'templates/order.html',
controller: 'ShowOrdersController',
resolve: {
orderId: function( $route ) {
return $route.current.params.orderId;
}
}
});
}]);
This will then honour the suggestion above, that the controller can declaratively specify its parameters:
app.controller("ShowOrderController", ["orderId", function (orderId, $scope) {
$scope.orderId = orderId;
}]);
In conjunction, this effectively decouples the controller from the route's parameters.
Using Angular I have a dozen or so routes setup similar to the following example code.
Is there a way to override which template and controller is loaded based on some other criteria while keeping the URL in tact? My goal is to display a login page when... lets say $scope.isLoggedIn = false. I don't want to change the URL to /login.
SomeApp.config(['$routeProvider', function($routeProvider) {
$routeProvider.
when('/place', {
templateUrl: 'routes/place.html',
controller: 'PlaceCtrl'
})
.when('/test', {
templateUrl: 'routes/test.html',
controller: 'TestCtrl'
});
}]);
ngRoute is a very simple library that can basically only maps urls to controller/views. If you want more flexibility, try ui-router which has the ability to route based on state.
This isn't really doable with ngRoute, but with ui-router you can dynamically provide different templates based on just about anything you want.
$stateProvider.state('root',
url: '/'
controller: 'HomePageController'
templateProvider: [
'$rootScope'
'$templateCache'
'$http'
($rootScope, $templateCache, $http) ->
templateId = if $rootScope.isLoggedIn then "home-page-logged-in" else "home-page-not-logged-in"
templateId = "/templates/#{templateId}.html"
return $http.get(templateId, cache: $templateCache)
]
)
The catch is, as far as I know, you can't change the controller, only the template. Which kinda stinks.
With ui-router, it's possible to inject either $state or $stateParams into a controller to get access to parameters in the URL. However, accessing parameters through $stateParams only exposes parameters belonging to the state managed by the controller that accesses it, and its parent states, while $state.params has all parameters, including those in any child states.
Given the following code, if we directly load the URL http://path/1/paramA/paramB, this is how it goes when the controllers load:
$stateProvider.state('a', {
url: 'path/:id/:anotherParam/',
controller: 'ACtrl',
});
$stateProvider.state('a.b', {
url: '/:yetAnotherParam',
controller: 'ABCtrl',
});
module.controller('ACtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id and anotherParam
}
module.controller('ABCtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id, anotherParam, and yetAnotherParam
}
The question is, why the difference? And are there best practices guidelines around when and why you should use, or avoid using either of them?
The documentation reiterates your findings here: https://github.com/angular-ui/ui-router/wiki/URL-Routing#stateparams-service
If my memory serves, $stateParams was introduced later than the original $state.params, and seems to be a simple helper injector to avoid continuously writing $state.params.
I doubt there are any best practice guidelines, but context wins out for me. If you simply want access to the params received into the url, then use $stateParams. If you want to know something more complex about the state itself, use $state.
Another reason to use $state.params is for non-URL based state, which (to my mind) is woefully underdocumented and very powerful.
I just discovered this while googling about how to pass state without having to expose it in the URL and answered a question elsewhere on SO.
Basically, it allows this sort of syntax:
<a ui-sref="toState(thingy)" class="list-group-item" ng-repeat="thingy in thingies">{{ thingy.referer }}</a>
EDIT: This answer is correct for version 0.2.10. As #Alexander Vasilyev pointed out it doesn't work in version 0.2.14.
Another reason to use $state.params is when you need to extract query parameters like this:
$stateProvider.state('a', {
url: 'path/:id/:anotherParam/?yetAnotherParam',
controller: 'ACtrl',
});
module.controller('ACtrl', function($stateParams, $state) {
$state.params; // has id, anotherParam, and yetAnotherParam
$stateParams; // has id and anotherParam
}
There are many differences between these two. But while working practically I have found that using $state.params better. When you use more and more parameters this might be confusing to maintain in $stateParams. where if we use multiple params which are not URL param $state is very useful
.state('shopping-request', {
url: '/shopping-request/{cartId}',
data: {requireLogin: true},
params : {role: null},
views: {
'': {templateUrl: 'views/templates/main.tpl.html', controller: "ShoppingRequestCtrl"},
'body#shopping-request': {templateUrl: 'views/shops/shopping-request.html'},
'footer#shopping-request': {templateUrl: 'views/templates/footer.tpl.html'},
'header#shopping-request': {templateUrl: 'views/templates/header.tpl.html'}
}
})
I have a root state which resolves sth. Passing $state as a resolve parameter won't guarantee the availability for $state.params. But using $stateParams will.
var rootState = {
name: 'root',
url: '/:stubCompanyId',
abstract: true,
...
};
// case 1:
rootState.resolve = {
authInit: ['AuthenticationService', '$state', function (AuthenticationService, $state) {
console.log('rootState.resolve', $state.params);
return AuthenticationService.init($state.params);
}]
};
// output:
// rootState.resolve Object {}
// case 2:
rootState.resolve = {
authInit: ['AuthenticationService', '$stateParams', function (AuthenticationService, $stateParams) {
console.log('rootState.resolve', $stateParams);
return AuthenticationService.init($stateParams);
}]
};
// output:
// rootState.resolve Object {stubCompanyId:...}
Using "angular": "~1.4.0", "angular-ui-router": "~0.2.15"
An interesting observation I made while passing previous state params from one route to another is that $stateParams gets hoisted and overwrites the previous route's state params that were passed with the current state params, but using $state.params doesn't.
When using $stateParams:
var stateParams = {};
stateParams.nextParams = $stateParams; //{item_id:123}
stateParams.next = $state.current.name;
$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{next:'app.details'}}
When using $state.params:
var stateParams = {};
stateParams.nextParams = $state.params; //{item_id:123}
stateParams.next = $state.current.name;
$state.go('app.login', stateParams);
//$stateParams.nextParams on app.login is now:
//{next:'app.details', nextParams:{item_id:123}}
Here in this article is clearly explained: The $state service provides a number of useful methods for manipulating the state as well as pertinent data on the current state. The current state parameters are accessible on the $state service at the params key. The $stateParams service returns this very same object. Hence, the $stateParams service is strictly a convenience service to quickly access the params object on the $state service.
As such, no controller should ever inject both the $state service and its convenience service, $stateParams. If the $state is being injected just to access the current parameters, the controller should be rewritten to inject $stateParams instead.