I'm using UI-Router module for routing. I have 2 states that router should match the urls with them :
// Dashboard
.state('dashboard', {
url: "/dashboard",
templateUrl: "dashboard/views/index.html",
controller: "DashboardController",
...
})
// Users
.state('users', {
url: "/users",
templateUrl: "users/views/index.html",
controller: "UsersController",
...
})
// Single User
.state('users.id', {
url: "/{id:^[a-z0-9_-]{3,16}$}",
templateUrl: "users/views/show.html",
controller: "UserController",
...
})
also I have set a default route :
$urlRouterProvider.otherwise("/dashboard");
I want the router to match users state when I go to http://127.0.0.1:8000/app/#/users
and to match user state when I go to http://127.0.0.1:8000/app/#/users/testuser
Problem :
the users state works good, but the user state url doesn't get matched and redirects to the default state. What's the problem?
There is a working example
Try to use this regex def:
.state('users.id', {
url: "/{id:(?:[a-z0-9_-]{3,16})}",
These links will work
<a href="#/users">
<a href="#/users/testuser">
This will go to otherwise
<a href="#/users/xx">
Some inspiration could be found here
In case, we want to go to state 'users.id' directly, we just have to use proper call. In this extended plunker, we can see that it could be done like this:
// working
<a ui-sref="users">
<a ui-sref="users.id({id:'testword'})">
// not working - id does not fit - otherwise is used
<a ui-sref="users.id({id:'not-working simply too long'})">
The same would be with $state.go('users.id', {id:'testword'})
Check it here
Here is an example of how I've done it in the past. Maybe it will help you.
app.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider,$rootScope,$cookieStore) {
$urlRouterProvider.otherwise("/login");
$stateProvider
.state('login', {
url: '/login',
title: 'Login',
templateUrl:'views/loginView.html',
controller: 'loginCtrl',
resolve: resolver($rootScope),
})
.state('account', {
url: '/account',
title: 'My Account',
accessLevel: 2,
resolve: resolver($rootScope,$cookieStore),
views: {
'navigation': {
templateUrl: 'views/navigationView.html',
controller: 'navigationCtrl'
},
'content': {
templateUrl: 'views/contentView.html',
controller: 'navigationCtrl'
}
}
})
.state('account.dashboard', {
url:'/dashboard',
title: 'Dashboard',
views : {
'pageContent': {
templateUrl:'views/dashboardView.html',
controller: 'dashboardCtrl'
}
}
})
.state('account.foo', {
url:'/foo',
title: 'foo',
views : {
'pageContent': {
templateUrl:'views/foo.html',
controller: 'fooCtrl'
}
}
})
.state('account.maps', {
url:'/maps',
title: 'Maps and stuff',
views : {
'pageContent': {
templateUrl:'views/mapsView.html',
controller: 'mapsCtrl'
}
}
})
}])
Related
I have configured the following ui-router.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal' },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
}])
app.controller('EditorCtrl', ['$scope', '$state', function ($scope, $state) {
$scope.layout = "horizontal";
$scope.$watch('layout', function () {
$state.go('global.editor.panels', { layout: $scope.layout });
});
}]);
As a result, https://localhost:3000/#/new in a browser leads to (the state global.editor, then to) the state global.editor.panels.
Now, I want to add a parameter connected:
I don't want it to be shown in the url
https://localhost:3000/#/new in a browser makes connected to be false, and https://localhost:3000/#/newTRUE in a browser makes connected to be true
connected can be past into the controller EditorCtrl and PanelsCtrl
connected can be available in the resolve of global.editor; ideally, we could resolve different objects according to the value of connected.
Does anyone know how to accomplish this?
You can add resolve for new and newTRUE:
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return false;
}
},
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
resolve: {
isConnected: function() {
return true;
}
},
controller: 'EditorCtrl'
})
And in EditorCtrl (or PanelsCtrl) you can use it like:
app.controller('EditorCtrl', ['$scope', '$state', 'isConnected', function($scope, $state, isConnected) {
console.log("connected : " + isConnected); // you can save this value in Service and use it later.
...
}]);
You can use classic approach - in resolve
Or you can use hidden parameters from angular ui router.
Define params : {isConnected' : null} in your parent global state.
In:
global.newTRUE - set value in $state config
global.new - set value in $state config
global.editor.panels - set parameters in transition/go or ui-sref
definition is like this:
$stateProvider
.state('global.newTRUE', {
url : '/:newTRUE',
params : {
'isConnected' : false
}
});
}
and in controller you get in from $stateParams.
Problem with this approach is hidden parameters are loses in refresh page, except if is set default value
You can surely use the params of UI-Router states' config to not show it in URL and achieve all mentioned points.
Also, as per #2, you need connected to be false for /new and true for /newTRUE. We can do so by passing true or false as default value for those states. Something like this:
$stateProvider
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return codeService.getPost($stateParams.id)
}]
}
}
.state('global.new', {
url: '/new',
templateUrl: '/htmls/editor.html',
params: { connected: false }, // default false for /new
controller: 'EditorCtrl'
})
.state('global.newTRUE', {
url: '/newTRUE',
templateUrl: '/htmls/editor.html',
params: { connected: true }, // default true for /newTRUE
controller: 'EditorCtrl'
})
.state('global.editor.panels', {
controller: 'PanelsCtrl',
params: { layout: 'horizontal', connected: null },
templateUrl: function (params) { return "/htmls/" + params.layout + '.html' }
}
For #3, In order to access connected in your controllers (EditorCtrl and PanelsCtrl) you can inject $stateParams to controller and use $stateParams.connected to get it.
For #4, (This is more or less similar to achieveing #3)
Just like you get $stateParams.id, you can have $stateParams.connected as well, which you can use to resolve different objects according to the value of connected. Something like this:
.state('global.editor', {
url: '/posts/editor/{id}',
templateUrl: '/htmls/editor.html',
params: { connected: null },
controller: 'EditorCtrl',
resolve: {
post: ['$stateParams', 'codeService', function ($stateParams, codeService) {
return $stateParams.connected ?
codeService.getPost($stateParams.id) :
codeService.getSomethingElse($stateParams.id)
}]
}
}
But, for that to work, make sure that you are passing connected as params when you visit global.editor state (using $state.go or ui-sref)
Hope this helps!
https://plnkr.co/edit/ByatrCzdUJfAV3oc8XPq?p=preview
^ On line 10, if you put back in the abstract:true key you will see the tags view appear in this plnkr app.
However my problem is that in my real app it won't let me use the abstract key because you first start at a login state and then transition to the dashboard state.
And the abstract key allows me to add the additional tags state as a child of dashboard.
When I have that key in there and I login in my real app this is the error I get:
Error: Cannot transition to abstract state 'dashboard'
Plnkr code:
var dash = {
name: 'dash',
url: '/dash?ticker',
// abstract: true,
views: {
'': { templateUrl: 'dashboard.html' },
'tickersList#dash': {
templateUrl: 'tickers-list.html',
controller: 'tickersController'
},
'alertsList#dash': {
templateUrl: 'alerts-list.html',
controller: 'alertsController'
}
}
};
var tags = {
name: 'dash.tags',
url: '?ticker',
params: {
ticker: 'AAA'
},
views: {
'tagsList#dash': {
templateUrl: 'tags-list.html',
controller: 'tagsController'
}
}
}
$stateProvider
.state(dash)
.state(tags);
Real app
LoginController:
$state.go('dashboard')
STATE_CONSTANTS:
dashboard state object:
.constant('STATE_CONSTANTS', {
dash: {
name: 'dashboard',
// abstract: true,
url: `/dashboard?ticker?start_epoch?end_epoch?timespan?group?sort?term_id_1?term_id_2?term_id_3?social?stream?links?retweets?tags_open?feed_open?chart_alerts?chart_max`,
views: {
'': {
templateUrl: 'dash/dashboard_container.html',
controller: function(UserFactory, container, user) {
this.container = container;
UserFactory.storeUser(user);
},
controllerAs: 'dc',
bindToController: true,
resolve: {
user: (AuthFactory) => AuthFactory.check_login(),
settings: (user, UserFactory) => UserFactory.settings(user),
container: ($stateParams, TagsFactory) => TagsFactory.createTerms($stateParams)
}
},
'platformHeader#dashboard': {
templateUrl: 'headers/platform/platform_header.html',
controller: 'PlatformCtrl',
controllerAs: 'ph'
},
'timespanHeader#dashboard': {
templateUrl: 'headers/timespan/timespan_header.html',
controller: 'TimeHeaderCtrl',
controllerAs: 'thc'
},
'tickersPanel#dashboard': {
templateUrl: 'tickers/panel/tickers_panel.html',
controller: 'TickersPanelCtrl',
controllerAs: 'tikp'
},
},
params: {
ticker: '',
},
data: { authorizedRoles: ['All'] }
},
login state object:
login: {
name: 'login',
url: '/login',
templateUrl: 'auth/login.html',
controller: 'LoginCtrl',
data: { authorizedRoles: ['All'] }
}
dashboard.html template
<div>
<header>
<div ui-view="platformHeader"></div>
<div ui-view="timespanHeader"></div>
</header>
<aside>
<!-- the headers and tickersPanel are all child states of
dashboard state -->
<div ui-view="tickersPanel"></div>
<!-- tags is a seperate state from dashboard -->
<div ui-view="tagsPanel"></div>
</aside>
//...
app.js
$stateProvider
.state(STATE_CONSTANTS.login)
.state(STATE_CONSTANTS.password)
.state(STATE_CONSTANTS.passwordreset)
.state(STATE_CONSTANTS.settings)
.state(STATE_CONSTANTS.settingsDefault)
.state(STATE_CONSTANTS.settingsAlerts)
.state(STATE_CONSTANTS.dash)
The behavior is right. You cannot transit to abstract state. Look at your example from plunker.
var dash = {
name: 'dash',
url: '/dash?ticker'
var tags = {
name: 'dash.tags',
url: '?ticker',
You have an abstract state "dash" and you have a child state "dash.tags" which is not abstract. So you can transit only to child state.
In your app, you try transiting to an abstract state which is not possible.
Abstract states are used if you want to have some basic state with common behavior (parent state). You cannot transit to such states but they can have some basic template, resolve functions... So, you have to remove abstract flag or create a child state.
My index.html page has 3 views:
<header ui-view="header"></header>
<main ui-view="content"></main>
<footer ui-view="footer"></footer>
I just changed the site to use these 3 views instead of the initial single view.
All the routes in my app work fine, for example the "home" view:
$stateProvider.state('home', {
url: '/home',
data: {
pageTitle: 'Home',
access: 'private',
bodyClass: 'home'
},
views: {
'header': {
templateUrl: 'modules/header/header.html'
},
'content': {
controller: 'HomeController as home',
templateUrl: 'modules/home/templates/home.html'
},
'footer': {
templateUrl: 'modules/footer/footer.html'
}
}
});
My issue is the "otherwise" state in the app does not correctly load the "home" state as it should. The page is blank, no console errors. Here's the state in my app.module:
angular.module('app').config(function ($stateProvider) {
$stateProvider.state('otherwise', {
url: '*path',
template: '',
data: {
pageTitle: '',
access: 'public',
bodyClass: ''
},
controller: function ($state) {
$state.go('home');
}
});
});
What am I missing here?
I am not sure if your wild char works or not.
Ideally I use the following for default routing:
$urlRouterProvider.otherwise('/app/home');
I'm building a mobile app with ionic, I'm facing a strange problem ..
If I reload the page ( F5 ) from, let say "/tabs/connected/channel/edit" I'm always redirected to "/tabs/home" ( after the state resolving ).
PS : The resolve phase is correclty executed and I never reject it. And then on promise resolving I'm always redirected to /tabs/home.
Here is my config block :
.config(function($stateProvider, $urlRouterProvider) {
var userResolve = function(user, $q, $auth) {
if (!$auth.isAuthenticated()) {
return $q.resolve();
}
if (user.loaded) {
return $q.resolve();
}
return user.blockingRefresh();
};
// Ionic uses AngularUI Router which uses the concept of states
// Learn more here: https://github.com/angular-ui/ui-router
// Set up the various states which the app can be in.
// Each state's controller can be found in controllers.js
$stateProvider
// setup an abstract state for the tabs directive
.state('tabs', {
url: '/tabs',
abstract: true,
templateUrl: 'templates/tabs.html'
})
.state('tabs.home', {
url: '/home',
views: {
'home-tab': {
templateUrl: 'templates/home.html',
controller: ''
}
}
})
.state('tabs.account', {
url: '/account',
views: {
'account-tab': {
templateUrl: 'templates/account/account.html',
controller: 'AccountController'
}
},
resolve: {
userData: userResolve
}
})
.state('tabs.login', {
url: '/account/login',
views: {
'account-tab': {
templateUrl: 'templates/account/login.html',
controller: 'AccountController'
}
}
})
.state('tabs.register', {
url: '/account/register',
views: {
'account-tab': {
templateUrl: 'templates/account/register.html',
controller: 'RegisterController'
}
}
})
.state('tabs.connected', {
url: '/connected',
abstract: true,
views: {
'account-tab': {
templateUrl: "templates/connected.html"
}
},
resolve: {
userData: userResolve
}
})
.state('tabs.connected.channel-create', {
url: '/channel/create',
templateUrl: 'templates/channel/create.html',
controller: 'CreateChannelController'
})
.state('tabs.connected.channel-edit', {
url: '/channel/edit',
templateUrl: 'templates/channel/edit.html',
controller: 'EditChannelController'
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/tabs/account');
})
Here is tabs.html :
<ion-tabs class="tabs-assertive tabs-icon-top">
<ion-tab title="Home" ui-sref="tabs.home" icon-on="ion-ios-filing" icon-off="ion-ios-filing-outline">
<ion-nav-view name="home-tab"></ion-nav-view>
</ion-tab>
<ion-tab title="Account" ui-sref="tabs.account" icon-on="ion-ios-gear" icon-off="ion-ios-gear-outline">
<ion-nav-view name="account-tab"></ion-nav-view>
</ion-tab>
I must precise that I never use $state.go('/tabs/home') I'm my code at all. I'm sure of that.
My goal is to stay on the same route even if I reload the app. ( This problem does not occurs on some state and I don't know why because they doesn't do anything different than the problematic ones .. )
Thank you !
I also got this type of error in Angular Ui-Router.
Below solution is work for me.
Just change it too
.state('tabs.connected.channelcreate', {
url: '/channelcreate',
templateUrl: 'templates/channel/create.html',
controller: 'CreateChannelController'
})
url should be
"/tabs/connected/channeledit"
And it will work.
I want to give 2 parts of my UI the same controller but still let them have each of their own unique controllers.
$stateProvider
.state('standard.page', {
url: '/:page',
resolve: {
page: function($stateParams) {
...
},
},
views: {
'content': {
templateUrl: '/tmpl/page',
controller: 'controllercontent'
},
'sideMenu': {
templateUrl: '/tmpl/menu',
controller: 'controllermenu',
}
}
})
So I want both content and sideMenu to share a controller. If I add a controller above the views then it requires a new template, I want to use the standard template instead of making a unique template for this state. Any ideas how I can get 3 controllers going in this example? Thanks.
I battled with this at some point in time, and I believe I made a template file that isn't directly accessible (via abstract: true). Here's an example...
.state('standard', {
url: '/standard',
abstract: true,
templateUrl: '/tmpl/standard.html',
controller: 'SharedController'
},
})
.state('standard.page', {
url: '/:page',
resolve: {
page: function($stateParams) {
...
},
},
views: {
'content': {
templateUrl: '/tmpl/page',
controller: 'controllercontent'
},
'sideMenu': {
templateUrl: '/tmpl/menu',
controller: 'controllermenu',
}
}
});
In your tmpl/standard.html file, make sure this exists somewhere within the file:
<div ui-view="sideMenu">
<div ui-view="content">
Hope this points you in the right direction.