https://plnkr.co/edit/VV13ty8XaQ20tdqibmFy?p=preview
Expected
After login the dashboard state renders dashboard.html, and all components and ui-views should render: tickers, tags, social(named ui-view) and feed.
Results
After login the dashboard state renders dashboard.html however only the components tickers,tags and feed show up, but not the social (named-ui-view)
I feel that my problem lies somewhere around where I transition from the login state to the dashboard state. Once you hit the dashboard state, it serves up the default template which is the component element tag: <dash-module></dash-module>. This will then render the dash.component template: dashboard.html and controller. However I've lost access to the social view in the dashboard state object.
dashboard.html
<div class="jumbotron text-center">
<h1>The Dashboard</h1>
</div>
<div class="row">
<tickers-module></tickers-module>
<tags-module></tags-module>
// Expecting the social-module-template.html to show below:
<div ui-view="social"></div>
<feed-module></feed-module>
</div>
The routerApp module with the dashboard component full code in Plnkr
// RouterApp module
////////////////////////////////////////////////////////////////////////////////
var routerApp = angular.module('routerApp', ['ui.router', 'tickers', 'tags', 'feed']);
routerApp.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/login');
const login = {
name: 'login',
url: '/login',
templateUrl: 'login.html',
bindToController: true,
controllerAs: 'l',
controller: function($state) {
this.login = function() {
$state.go('dashboard', {});
}
}
}
const dashboard = {
name: 'dashboard',
url: '/dashboard',
params: {
ticker: {},
tags: {}
},
template: '<dash-module></dash-module>',
views: {
'' : {
templateUrl: 'dashboard.html',
},
'social' : {
templateUrl: 'social-module-template.html',
controller: function($state) {
console.log('Social init', $state.params);
}
}
}
}
$stateProvider
.state(login)
.state(dashboard);
})
tags.component('dashModule', {
templateUrl: 'dashboard.html',
controller: function($scope, $state) {
console.log('dashModule loaded!');
}
})
This is the part that should render the social html content in the <div ui-view="social"></div>
views: {
'' : {
templateUrl: 'dashboard.html',
},
'social' : {
templateUrl: 'social-module-template.html',
controller: function($state) {
console.log('Social init', $state.params);
}
}
}
I made changes to your plunker here You were missing # here.
const dashboard = {
name: 'dashboard',
url: '/dashboard',
params: {
ticker: {},
tags: {}
},
template: '<dash-module></dash-module>',
views: {
'' : {
templateUrl: 'dashboard.html',
},
'social#dashboard' : {
templateUrl: 'social-module-template.html',
controller: function($state) {
console.log('Social init', $state.params);
}
}
}
}
In order for these components to appear under the home state, we must define them using absolute naming. Specifically, we must use the # syntax to tell AngularJS that these components of our application should be mapped to a specific state. This follows the viewName#stateName syntax and tells our application to utilize named views from an absolute, or specific state. You can read more about relative vs. absolute names here.
See this for more information.
The problem you have is named view has to render in same state i.e Dashboard.
Change the following and it should work.
social#dashboard
Check this Plunkr
Named Views UI router
Related
I would like to have a common state with all the common views like the header and the sidebar, and a template where I would like to load different views that can change when the state is changing.
I have an index HTML file like this:
...
<div ui-view="header"></div>
<div ui-view="sidebar"></div>
<div class="container">
<div class="wrapper">
<div ui-view="content"></div>
</div>
</div>
...
While the AngularJS config is something like this:
$stateProvider
.state('mainCommonState', {
url: '',
abstract: true,
views: {
header: {
templateUrl: 'app/common/header.html',
controller: 'headerCtrl',
controllerAs: 'vm'
},
sidebar: {
templateUrl: 'app/common/sidebar.html',
controller: 'sidebarCtrl',
controllerAs: 'vm'
},
content: {}
},
resolve: {
apiEnvironment: function ($q, environmentApiService) {
var deferred = $q.defer();
deferred.resolve(environmentApiService.getApiEnvironment());
return deferred.promise;
}
}
})
.state('first-page-content', {
url: '/first-page-content',
parent: 'mainCommonState',
views: {
content: {
templateUrl: 'app/components/first-page-content.html'
controller: 'firstPageCtrl',
controllerAs: 'vm'
}
}
})
.state('second-page-content', {
url: '/second-page-content',
parent: 'mainCommonState',
views: {
content: {
templateUrl: 'app/components/second-page-content.html'
controller: 'secondPageCtrl',
controllerAs: 'vm'
}
}
})
.state('third-page-content', {
url: '/third-page-content',
parent: 'mainCommonState',
views: {
content: {
templateUrl: 'app/components/third-page-content.html'
controller: 'thirdPageCtrl',
controllerAs: 'vm'
}
}
})
For some reason this is not working: I have an empty view instead of the 3 templates that I would like to show in the content ui-view.
If I define a template (even a blank template) inside the the abstract state, the view that is always showing is the one inside the abstract state mainCommonState.
Where am I wrong?
1st Edit: UPDATE Following the first answer
Following the suggestion from Chris T, I have updated my code, but there still something missing.
I have created a Plunker so you can help me fixing the issues.
2nd Edit
Following the suggestions from Chris T, I have updated the code using the absolute path for the content view and now the contents are switching correctly.
I have updated the Plunker accordingly to that and introduced a new level of nesting view (tabs in the first page), and I would like to have the first tab active when the first page content is loaded.
If I follow these solutions and set empty the url of the first page and set it to the first tab instead, this is not working.
Any suggestions?
Your views are targeting the wrong named ui-view.
.state('second-page-content', {
url: '/second-page-content',
parent: 'mainCommonState',
views: {
content: {
templateUrl: 'app/components/second-page-content.html'
controller: 'secondPageCtrl',
controllerAs: 'vm'
}
}
})
In this snippet, it targets the ui-view named content from the parent state which is mainCommonState. However, the content ui-view was not created in the mainCommonState. It was created in the root template.
Change your view declarations to target the view at the correct state, for example this targets the content view at the root state (which is named empty string):
.state('second-page-content', {
url: '/second-page-content',
parent: 'mainCommonState',
views: {
'content#': {
templateUrl: 'app/components/second-page-content.html'
controller: 'secondPageCtrl',
controllerAs: 'vm'
}
}
})
In ui-router 1.0 and higher you can also use absolute ui-view names by prefixing with an exclamation
.state('second-page-content', {
url: '/second-page-content',
parent: 'mainCommonState',
views: {
'!content': {
templateUrl: 'app/components/second-page-content.html'
controller: 'secondPageCtrl',
controllerAs: 'vm'
}
}
})
Read more about view targeting in the UI-Router views guide:
https://ui-router.github.io/guide/views#view-name-only
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 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'
}
}
})
}])
I have a single-page AngularJS application with four regions, each with its own content:
I need each region to communicate via services, but otherwise they need to have their own independent routing for view purposes i.e. they should each have their own view state.
I have tried to do this (plunkr) with angular-ui-router but I can't figure out how to create angular-ui states that affect only a particular module or region, without modifying the rest of the regions on the page.
The page contains the regions:
<body>
<a ui-sref="initial1">Initial Region 1</a><br/>
<a ui-sref="initial2">Initial Region 2</a>
<div ui-view="region1" class="region1"></div>
<div ui-view="region2" class="region2"></div>
</body>
And the app attempts to define each region in an independent module:
var app = angular.module('Main', ['ui.router', 'Region1', 'Region2']);
var region1App = angular.module('Region1', []);
region1App.config(function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('initial1', {
url: '/',
views: {
'region1#': {
template: 'Initial Region 1 State, go to <a ui-sref="second1">Second State</a>'
}
}
})
.state('second1', {
url: '/',
views: {
'region1#': {
template: 'Second Region 1 State, go to <a ui-sref="initial1">Initial State</a>'
}
}
});
});
var region2App = angular.module('Region2', []);
region2App.config(function($urlRouterProvider, $stateProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state('initial2', {
url: '/',
views: {
'region2#': {
template: 'Initial Region 2 State, go to <a ui-sref="second2">Second State</a>'
}
}
})
.state('second2', {
url: '/',
views: {
'region2#': {
template: 'Second Region 2 State, go to <a ui-sref="initial2">Initial State</a>'
}
}
});
});
Each module should have its own "initial" state and "second" state, and both should show on the screen at the same time, and changing the state of one should not affect the other. If this cannot be done with angular-ui-router, what is the best way to do this with Angular?
You can use UI-Router Extras - sticky states to achieve your goal.
You'll want one named <div ui-view='name'></div> for each region. Then, add sticky: true to the state definition which targets that region's named view.
<div ui-view="region1"></div>
<div ui-view="region2"></div>
<div ui-view="region3"></div>
<div ui-view="region4"></div>
.state('state1', {
sticky: true,
views: { region1: { templateUrl: 'foo.html', controller: barCtrl } }
}
.state('state2', {
sticky: true,
views: { region2: { templateUrl: 'foo2.html', controller: bar2Ctrl } }
}
.state('state3', {
sticky: true,
views: { region3: { templateUrl: 'foo3.html', controller: bar3Ctrl } }
}
.state('state4', {
sticky: true,
views: { region4: { templateUrl: 'foo4.html', controller: bar4Ctrl } }
}
There is a demo you can view which shows how this works. Note: the demo uses tabs and shows/hides the ui-views accordingly. Your use case does not need to show/hide each named view.
Check out the demo source code for more.
I created a separate angular app for each region. Communication across applications is done via obtaining a reference to the relevant scope via the app element in the DOM, and sending an event via angular.element(document.getElementById('RegionX_App')).scope().$emit as shown here.
UPDATE: I ended up using Sticky States in UI-Router Extras as described in the answer by Chris T, and it worked perfectly.