AngularJS : UI-router setting configurations using directives - angularjs

I understand the configuration settings for UI-router and it is working fine. Now I am trying to move the following configuration to directives. So that, the length of code will be reduced in js file. May be, it is poor design but I want to achieve this :)
Current Configuration (As per UI-router design)
//Router Configuration
angular.module('myApp', ['ui.router']).config(function($stateProvider) {
$stateProvider.state('addCustomer', {
url: "/addCustomer",
templateUrl: 'views/customer/add_customer.html',
controller: 'addCustomerController'
});
...No of configuration, list is very big...
});
//In Template
<a ui-sref="addCustomer">Add Customer</a>
What I am trying to change
//Router Configuration
angular.module('myApp', ['ui.router']).config(function($stateProvider) {
});
//In Template
<a ui-sref="addCustomer" router-info="{'url':'/addCustomer', 'templateUrl':'views/customer/add_customer.html', 'controller':'addCustomerController'}">Add Customer</a>
//Directive- Dynamic routing
angular.module('myApp').directive('routerInfo', function(){
var directive = {};
directive.restrict = 'A';
directive.compile = function(element, attributes) {
var linkFunction = function($scope, element, attributes) {
//Here I understand, I can not inject stateprovider. So kindly suggest any other way to do this
//$stateProvider.state(attributes.uiSref, attributes.routerInfo);
}
return linkFunction;
}
return directive;
});
How to add UI-router configuration from directives? Is there any API available to set? or any other better way to handle this... My intention is to reduce code in config block.

If you're trying to avoid having one giant config block, simply use multiple config blocks and put each one in its own file. There's no reason not to put configuration code in a config block, it just sounds like you need to approach it a better way, so split it up into smaller blocks.
Example:
// config/routes/user.routes.js
angular.module('yourModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('user', {
url: '/users/:userName',
abstract: true,
// etc
})
.state('user.main', {
url: '',
//etc
})
.state('user.stuff', {
url: '/stuff',
// etc
})
;
}
])
;
And repeat for each set of routes:
// config/routes/more.routes.js
angular.module('yourModule')
.config([
'$stateProvider',
function($stateProvider) {
$stateProvider
.state('more', {
url: '/more-routes',
//etc
})
;
}
])
;

Related

Set AngularJS Global before App Initialization

I am currently working in an AngularJS code base where different routes are triggered depending on some toggle element attached to every customer. In my app, I need to route to different places twice, once, in my app.js file, where I manage my state and another time, in a controller when I use the same variable to branch.
I have tried to use a combination of manually bootstrapping and using the run method but haven't had any success.
var app = angular
.module('app', ['customer'])
.run(function (customer) {
angular.element(document).ready(function () {
customer.myMethod().then(function (enabled) {
enabled = enabled;
angular.bootstrap(document, ['app']);
});
});
});
$stateProvider
.state('home', {
url: enabled ? '/' : '/customer',
templateUrl: 'views/home.html',
controller: 'HomeCtrl',
data: {
title: 'Home'
}
})
And then in my controller, use the same variable in my init method for example:
$scope.init = function () {
if (enabled) { ... }
else { ... }
}
$scope.init();
I notice that the enabled variable eventually does get set, but it happens after the state is decided. Is there a way to set a global variable before my AngularJS app starts in order to use it anywhere?
Thanks

Why am I able to assign a variable to $rootScope in an Angular controller and access it in the template, but not if I assign the variable to $scope?

I am using the Angular UI Router as my router. I have a $state defined as such:
$stateProvider.state('mypage', {
url:'/',
views: {
'content': {
templateUrl: 'folder/mypage.template.html',
controller: 'MyPageController'
}
}
})
I can go to MyPageController and do the following:
$rootScope.test = "hello!";
And then go to folder/mypage.template.html and put the following:
<div id="example">
{{test}}
</div>
hello! will show up in the rendered web page. However, if I instead do the following in MyPageController:
$scope.test = "hello!";
Nothing will show up in the template. This is very confusing to me, as I know that MyPageController is made available to the state (as I can add something to $rootScope and display it), but the $scope is not available. Does anyone have an idea as to what might be going on? Thank you :)
EDIT1:
MyPageController is part of a module, let's say myModule, that is imported into a top-level module. For example, it looks something like this:
angular.module('topLevelModule', [
'myModule'
]).config( ... $stateProvider stuff ... ).run( ... setup stuff ... )
angular.module('myModule')
.controller('MyPageController', ['$scope', '$rootScope', function($scope, $rootScope) {
$scope.test = "hello!";
}]);
EDIT2 (problem solved):
I had followed a tutorial that used the following pattern with multiple states in the UI-Router:
$stateProvider
.state('mainpage', {
url: '/',
views: {
'content': {
templateUrl: 'folder/mainpage.template.html',
controller: 'MainPageController' <-- POINT OF INTEREST 1
}
}
})
.state('mypage', {
url: 'my-page',
controller: 'MyPageController', <-- POINT OF INTEREST 2
views: {
'content#': {
templateUrl: 'folder/mypage.template.html',
<-- POINT OF INTEREST 3
}
}
})
}])
However, the problem lies in this formatting. This is just a heads up for anyone using the UI-Router who happened to follow the same tutorial as I did (I can't find the link), that POINT OF INTEREST 2 needs to be moved to POINT OF INTEREST 3 for the controller to properly be assigned upon state change -- syntax error. There were more complexities to why things were happening (due to my debugging approach) that were causing any inconsistencies you see above, but I won't include them here. Thanks everyone for their time. :)
var test = angular.module('test', []);
test.run(function() {
});
test.config(function() {
// your state for binding html with controller
});
test.controller('MyPageController ', function($scope) {
$scope.test = "hello!";
});
And then in HTML
<div id="example">
{{test}}
</div>

Is it possible to replace/overwrite a ui-router state definition? (default state being "customized" by another module)

Is it possible to simply replace a state definition with a new one?
The use case I have is that one module defines the state, and I'd like to be able to replace that state definition from a different module. Basically the initial definition is a "default" definition, and I'd like to have the possibility to customize the state from a different module.
I realize that doing this at config time could result in a race condition on which module defines it first. I looked into the possibility of doing it at run-time, but couldn't get it working.
I can accomplish what I want by creating entirely new states, but I would like to use the same URL from the original state.
Edit:
To further clarify what I am trying to accomplish...
Please consider the following code:
angular.module('module1', ['ui.router'])
.config(['$stateProvider', function($stateProvider){
$stateProvider
.state('root', {
url: '',
abstract: true,
template: '<div class="layout sub" ui-view="sub"></div>'
})
.state('root.sub1', {
url: '/sub1',
views: {
'sub#root': {
template: '<p>Default sub state 1</p>'
}
}
});
}]);
angular.module('module2', ['ui.router'])
.config(['$stateProvider', function($stateProvider){
$stateProvider
.state('root.sub1', {
url: '/sub2',
views: {
'sub#root': {
template: '<p>Customized sub state 1</p>'
}
}
});
}]);
Which of course gives "Error: State 'root.sub1'' is already defined"
Ok, so as I was putting together a plnkr demo of what I tried next, I found that it actually works if you use $state.get and update it.
angular.module('module2', ['ui.router'])
.run(['$state', function($state){
var state = $state.get('root.sub1');
if(state && state.views && state.views['sub#root']){
state.views['sub#root'].template = '<p>Customized sub state 1</p>';
}
}]);
Plnkr: http://plnkr.co/edit/xLdCgjeM33z2Hf5CHfZR
Edit:
I figured out that it wasn't working in my app because I didn't define the view I wanted to override in the original state.
Example (doesn't work):
angular.module('module1', ['ui.router'])
.config(['$stateProvider', function($stateProvider){
$stateProvider
.state('root', {
url: '',
abstract: true,
template: '<div class="layout sub" ui-view="sub"></div>'
})
.state('root.sub1', {
url: '/sub1',
views: {
}
});
}]);
angular.module('module2', ['ui.router'])
.run(['$state', function($state){
var state = $state.get('root.sub1');
if(state && state.views){
state.views['sub#root'] = { template: '<p>Customized sub state 1</p>' };
}
}]);
it seems like there is no way to overwrite it for < v1, unless you need to rewrite the decorator for the stateProvider.
for v1, there is a new method deregister for that purpose
https://github.com/angular-ui/ui-router/commit/44579ecbafe15ad587dc341ba4a80af78b62bdaa
I tried using $state.get(view) and overwrite the value, but the controller and template are not registered properly because the built-in decorator is using stateBuilder property to generate the controller and template.
check https://github.com/angular-ui/ui-router/blob/0.3.2/src/state.js
The best approach I've found to solving this is to:
Create a 'root' controller (ie. a state that loads prior to other states)
.state('root' , { url: ':/root_path', controller: 'rootController' ..other stuff here })
In rootController, you decide what to do with the route:
db.call(pull-root-key-from-db) .then(function(root_response) {
if (root_response.type == "user") {
$state.go("user.dashboard") })
Sorry for formatting, I'm mobile :)

Angularjs and dynamic routes

I am trying to create a link in my template angularjs by doing something like:
<a ng-href="/#!/content/[[value.id]]">[[key]]</a>
But I am wondering myself if is possible do something like symfony2 does, example:
routing.yml
home_redirect:
path: /
defaults:
_controller: FrontendBundle:Controller:function
path: /home
permanent: true
options:
expose: true
And using it in your twig template by doing:
one link to home
That is really, really helpful because I don't have to "hardcode" all my routes.
To ensure a proper routing, you can use ui-router.
Here is an exemple on plunker
How this works :
1 - Follow the installation guide on their github
2 - Write your state definition :
app.config(function($stateProvider, $urlRouterProvider){
//If no route match, you'll go to /index
$urlRouterProvider.otherwise('/index');
//my index state
$stateProvider
.state('index', {
url: '/index',
templateUrl: 'index2.html',
controller: 'IndexCtrl'
})
//the variable state depending on an url element
.state('hello', {
//you will be able to get name with $stateParams.name
url: '/hello/:name',
templateUrl: 'hello.html',
controller: 'HelloCtrl'
})
});
3 - Write links by their state name :
//add this directive to an html element
//This will go to /index
ui-sref="index"
//This will go to /hello/
ui-sref="hello"
//This will go to /hello/ben
ui-sref="hello({name:'ben'})"
//This will go to /hello/{myname}
ui-sref="hello({name:myname})"
4 - Get the param into your controller :
//inject $stateParams
app.controller('HelloCtrl', function($scope, $stateParams){
$scope.controller = "IndexCtrl";
//get the param name like this
$scope.name = $stateParams.name;
});
Hope it helped. Also keep in mind the ui-router got some really powerful tools such as resolve and nested state/view. You'll probably need theses now or later.
PS : If the plunker don't work, just fork it and save again.
You could do this :
'use strict';
angular.module('AngularModule')
.config(function ($stateProvider) {
$stateProvider
.state('YourStateName', {
url: '/your/url',
views: {
'aViewName': {
templateUrl:'views/components/templates/yourTemplate.html',
controller: 'YourController'
}
},
resolve: {
}
});
});
// then in your controller
angular.module('AngularModule')
.controller('MyController',function($scope, $state){
$scope.goTo = function(){
$state.go('YourStateName');
}
}
);
//in your html make sure the <a> tag is in scope with the 'MyController'
<a ng-click='goTo'>[[key]]</a>
or
you can just do this :
<a ng-href="/your/url"></a>
that way you bypass the controller you can still put logic in the controller that was specified in the state

ui-router with customer controller and ng-strict-di

so, following best practice I've started using ng-strict-di. It's worked well so far, but I have hit the following problem using ui-router
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: function($scope) {
$scope.dogs = ['Bernese', 'Husky', 'Goldendoodle'];
}
})
this causes angular to barf with the "Error: error:strictdi
Explicit annotation required" error.
I know that I should be using the inline bracket notation, or $inject, but obviously can't put it in this code as is.
I was thinking that I could declare the controller in another part of the script, with $inject and then just reference it in the code ?
function GoodController1($scope) {
}
GoodController1.$inject = ["$scope"];
and then
// nested list with custom controller
.state('dashboard.list', {
url: '/list',
templateUrl: 'partials/dashboard-list.html',
controller: GoodController1
})
would this work ? Are there any problems with this approach ?
There are no problems, with this approach. I am using typescript, and the generated syntax of controlelr class is almost the same as yours.
Here is a working plunker
...
// the contoller funciton to be instantiated
// by angular using new
var GoodController1 = function($scope){
$scope.title = "good title";
};
// set of dependencies
// (in typescript that would be a static property)
GoodController1.$inject = ["$scope"];
// before angular 2.0, this is the must
// we still have to register controller in the module
app
.controller('GoodController1', GoodController1)
...
and later in state:
.state('good', {
url: "/good",
templateUrl: 'tpl.html',
controller: "GoodController1",
})
check it here

Resources