I am using ui-router and I try to create several views inside one of my nested states.
In my logic, all of the views should be visible whenever the parent state is active.
I have read the wiki page on multiple named views multiple times, but could not get anything working yet.
My state template would show up, but my views never do.
Here is a working plunker
(You have to click on "Followers" in the navbar for the view to show up. Haven't figured why yet).
Important parts are the config
app.config([
'$stateProvider',
'$urlRouterProvider',
function ($stateProvider, $urlRouterProvider){
$stateProvider
.state('dashboard', {
url: '/dashboard',
templateUrl: 'dashboard.html',
controller: 'DashboardCtrl',
authenticate: true
}).state('dashboard.followers', {
url: '/followers',
templateUrl: 'dashboard.followers.html',
controller: 'DFollowersCtrl',
authenticate: true
}).state('dashboard.followers.add', {
views: {
'add': {
templateUrl: 'dashboard.followers.add.html',
controller: 'DFollowersAddCtrl',
authenticate: true
}
},
});
$urlRouterProvider.otherwise('dashboard');
}
]);
The main dashboard template (level 2, using a generic ui-view)
<div class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main">
<div class="flash-area">
<flash-message duration="2000" show-close="true"></flash-message>
</div>
<ui-view></ui-view>
</div>
and the dashobard.followers specific level 3 view, that has a specific name
<div>
<h1 class="page-header">Followers</h1>
<div ui-view="add"></div>
</div>
The trick is coming from, I think, a combination of :
I want to use level 3 nesting
Level 2 uses a generic ui-view, because it may contains my dashboard or something else.
Level 3 contains specific views, as it is where I want to use "views" and not "states" (as far as I understood, at least).
The final aim is to have more than one view in my template, but for now I reduced the attempts to only show the 'add' view.
I have seen several similar questions on SO already, such as this one or this other one but so far my attempts have not been fruitful.
I can access my "add" view directly if I reach its URL (when I try setting one)
But the dashboard.followers state does not get populated by the views.
I think there are several mistakes here you made:
if you want the url of dashboard.followers state and dashboard.followers.add state to be same, the child state dashboard.followers.add does not need the url option
probably can be a mistake(I am not sure because no code is provided), if you don't use the
views: { ... }
named views, but just directly use
url: '/followers',
templateUrl: '/partials/dashboard.followers.html'
angular just assume you want to insert the template in an unnamed <div ui-view></div> in the parents state's template not the root template. for example, in my example code, for state dashboard.followers, since it is a child state of dashboard, if I want to insert the template in root html template, I have to use
views: {
'#': {
template: '<div><h1 class="page-header">Followers</h1><a ui-sref="dashboard.followers.add">add</a><div ui-view="add"></div></div>'
}
}
/* myApp module */
var myApp = angular.module('myApp', ['ui.router'])
.config(['$stateProvider', function($stateProvider) {
$stateProvider
.state('landing', {
url: '/',
template: '<p>landing</p><a ui-sref="dashboard">dashboard</a>'
})
.state('dashboard', {
url: '/dashboard',
template: '<p>landing</p><a ui-sref="dashboard.followers">followers</a>'
})
.state('dashboard.followers', {
url: '/followers',
views: {
'#': {
template: '<div><h1 class="page-header">Followers</h1><a ui-sref="dashboard.followers.add">add</a><div ui-view="add"></div></div>'
}
}
})
.state('dashboard.followers.add', {
views: {
'add': {
template: '<p>followers</p>'
}
}
});
}])
.controller('MyAppCtrl', function($scope, $state /*, $stateParams*/ ) {
$state.go("landing");
});
<body ng-app="myApp" ng-controller="MyAppCtrl">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.18/angular-ui-router.js"></script>
<div ui-view></div>
</body>
update
I made two plunkers to fit two different situation:
if you want to dynamically load add state to ui-view="add" by a
link, check
this out.
if you just want a sub template to be loaded always on dashboard.followers state, simply remove add states, and use views: {...} to load the add template in. here is the plunker.
Related
I have a question regarding Angular UI-Router and its ui-views. I declare three ui-views inside another one, and the only one that shows up is the one with the name "languages". I don't understand why this happens, and if anybody could help that would be great.
index.html:
<div ui-view="languages">
<div ui-view="dashboard"></div>
<div ui-view="partners"></div>
<div ui-view="footer"></div>
</div>
routes.js:
angular.module('TMHM')
.config(routeConfig);
routeConfig.$inject = ['$stateProvider', '$urlRouterProvider'];
function routeConfig ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('home', {
url: '/',
views: {
'languages': {
templateUrl: 'views/languages/languages.html'
},
'dashboard': {
templateUrl: 'views/dashboard/dashboard.html'
},
'partners': {
templateUrl: 'views/partners/partners.html'
},
'footer': {
templateUrl: 'views/footer/footer.html'
}
}
});
$urlRouterProvider.otherwise('/');
};
Here's the Plunker code, although I couldn't get that to work:
https://plnkr.co/edit/z8cFGHKVQNN623QbBUqi
There is updated and working plunker https://plnkr.co/edit/vKOr2yLUfaAfwoGyK0ws?p=preview
I created new routes.html, with this content
<h1>Routes</h1>
<hr />
<div ui-view="languages"></div>
<div ui-view="dashboard"></div>
<div ui-view="partners"></div>
<div ui-view="footer"></div>
And changed index.html to contain
<div ui-view=""></div>
And then state adjustment is:
.state('home', {
url: '/',
views: {
'': {
templateUrl: 'routes.html'
},
'languages#home': {
templateUrl: 'languages.html'
},
'dashboard#home': {
templateUrl: 'dashboard.html'
},
'partners#home': {
templateUrl: 'partners.html'
},
'footer#home': {
templateUrl: 'footer.html'
}
}
});
Also, essential was move the ng-app from <head> to <html> element
<html ng-app="TMHM">
<head >
check it here
More details and examples about named and multi views:
Nested states or views for layout with leftbar in ui-router?
Angular UI Router - Nested States with multiple layouts
I've never seen that done this way before (a routed view in a routed view). That may be because it doesn't work, or I've just never run into it, I don't know. I tend to think of views as being top level, and then includes as being the nested content.
If that's the idea, I've done something very similar to this, but I used ng-include (I currently have this in production in an app serving a lot of users):
<div ng-include="mycontroller.pathToFileIWantToShow"></div>
// alternatively, although hardcoding can be evil...
<div ng-include="path/to/some/file.html"></div>
This allows me to change the content dynamically, use binding etc. etc, and each included template can reference its own controller, or just use the controller that wraps it. It doesn't seem to matter how many I nest.
Building a multi-step form (“wizard”). Was originally following this tutorial, which worked great, but am now trying to adapt it so step one is embedded on the homepage rather than being a separate state. No matter what I try, I can not create a ui-sref path that will work. I always get:
Could not resolve '.where' from state 'home'
or
Could not resolve 'wizard.where' from state 'home'
or
Could not resolve 'wizard.where#' from state 'home'
…even though wizard.where# works fine in <div ui-view="wizard.where#"></div>. What is the correct syntax?
Here are the relevant files:
home.js (left comments intact so you can see various methods I’m trying):
var wizard = {
url: '/home/wizard',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard.tpl.html'
};
angular.module( 'myApp.home', [
'ui.router',
'ui.bootstrap',
'myApp.modal',
'angularMoment'
])
.config(function config( $stateProvider, $urlRouterProvider ) {
$stateProvider
.state( 'home', {
url: '/home',
views: {
"main": {
controller: 'HomeCtrl',
templateUrl: 'home/home.tpl.html'
},
"jumbotron": {
controller: 'HomeCtrl',
templateUrl: 'home/welcome.tpl.html'
},
"wizard": wizard,
"wizard.where": {
url: '/home/wizard/where',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-where.tpl.html',
parent: wizard
},
"wizard.what": {
url: '/home/wizard/what',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-what.tpl.html',
parent: wizard
},
"wizard.when": {
url: '/home/wizard/when',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-when.tpl.html',
parent: wizard
},
},
data: { pageTitle: 'Home' }
})
// route to show our basic form (/wizard)
// .state('wizard', {
// url: '/wizard',
// views: {
// "main": {
// controller: 'VendorsCtrl',
// templateUrl: 'vendors/wizard.tpl.html'
// }
// },
// abstract: true,
// //data: { pageTitle: 'Vendor Search' }
// })
// nested states
// each of these sections will have their own view
// url will be nested (/wizard/where)
// .state('wizard.where', {
// url: '/where',
// templateUrl: 'vendors/wizard-where.tpl.html'
// })
// url will be /wizard/when
// .state('wizard.when', {
// url: '/when',
// templateUrl: 'vendors/wizard-when.tpl.html'
// })
// url will be /wizard/vendor-types
// .state('wizard.what', {
// url: '/what',
// templateUrl: 'vendors/wizard-what.tpl.html'
// })
;
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/home/wizard/where');
})
wizard.tpl.html:
<div class="jumbotron vendate-wizard" ng-controller="VendorsCtrl as vendorsCtrl">
<header class="page-title">
<h1>{{ pageTitle }}</h1>
<p>Answer the following three questions to search available vendors. All answers can be changed later.</p>
<!-- the links to our nested states using relative paths -->
<!-- add the active class if the state matches our ui-sref -->
<div id="status-buttons" class="text-center">
<a ui-sref-active="active" ui-sref="wizard.where#"><span>1</span> Where</a>
<a ui-sref-active="active" ui-sref="wizard.what#"><span>2</span> What</a>
<a ui-sref-active="active" ui-sref="wizard.when#"><span>3</span> When</a>
</div>
</header>
<!-- use ng-submit to catch the form submission and use our Angular function -->
<form id="signup-form" ng-submit="processForm()">
<!-- our nested state views will be injected here -->
<div id="form-views" ui-view="wizard.where#"></div>
</form>
</div>
wizard.where.tpl.html:
<div class="form-group">
<label class="h2" for="where">Where Is Your Wedding?</label>
<p id="vendor-where-description">If left blank, vendors in all available locations will be shown.</p>
<div class="input-group-lg">
<input id="where" ng-model="formData.where" class="form-control" type="text" placeholder="Boston, MA" aria-describedby="vendor-where-description" />
</div>
</div>
<ul class="list-inline">
<li>
<a ui-sref="wizard.what#" class="btn btn-block btn-primary">
Next <span class="fa fa-arrow-right"></span>
</a>
</li>
</ul>
I created working plunker here
NOTE: You should read about state nesting and named views more. Because the current state and view definition is simply wrong.
Nested States & Nested Views
Multiple Named Views
Firstly, we should not use the ONE state definition with many views: {}. But we should split them into real states. Hierarchy will have three levels
The first level - super root state
.state( 'home', {
url: '/home',
views: {
"main": {
controller: 'HomeCtrl',
templateUrl: 'home/home.tpl.html'
},
}
})
The second level - wizzard, check that now we change the url. We will inherit its first part from our parent (home)
.state("wizard", {
parent: 'home',
//url: '/home/wizard',
url: '/wizard',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard.tpl.html'
})
The third level - all where, what, when now will also inherit url. They do not have to define parent, because it is part of their names
.state( "wizard.where", {
//url: '/home/wizard/where',
url: '/where',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-where.tpl.html',
//parent: wizard
})
.state( "wizard.what", {
//url: '/home/wizard/what',
url: '/what',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-what.tpl.html',
//parent: wizard
})
.state( "wizard.when", {
//url: '/home/wizard/when',
url: '/when',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-when.tpl.html',
//parent: wizard
})
Parent wizzard must now contain unnamed view target ui-view=""
<div ui-view=""></div>
Current wizard.tpl.html contains this:
<!-- our nested state views will be injected here -->
<div id="form-views" ui-view="wizard.where#"></div>
The sign # should be avoided, because it could be used for absulte view naming - BUT inside of the state defintion. So, what could work is ui-view="someName
<!-- our nested state views will be injected here -->
<div id="form-views" ui-view="someName"></div>
Now, these are (in example here) view content of the home.tpl
<div>
<h1>HOME</h1>
<div ui-view=""></div>
</div>
And wizzard.tpl
<div>
<h2>WIZZARD</h2>
<div ui-view=""></div>
</div>
So, we have unnamed view target inside of home and wizard states, That is very handy, because we can use the light state definition, without views : {} object. And that is always preferred in case we do not have multi-views.
That means, that this state definition will properly be injected into above template:
// no views - search in parent for a ui-view=""
...
.state( "wizard.when", {
url: '/when',
controller: 'VendorsCtrl',
templateUrl: 'vendors/wizard-when.tpl.html',
})
...
Check the doc:
View Names - Relative vs. Absolute Names
Behind the scenes, every view gets assigned an absolute name that follows a scheme of viewname#statename, where viewname is the name used in the view directive and state name is the state's absolute name, e.g. contact.item. You can also choose to write your view names in the absolute syntax.
For example, the previous example could also be written as:
.state('report',{
views: {
'filters#': { },
'tabledata#': { },
'graph#': { }
}
})
Notice that the view names are now specified as absolute names, as opposed to the relative name. It is targeting the 'filters', 'tabledata', and 'graph' views located in the root unnamed template. Since it's unnamed, there is nothing following the '#'. The root unnamed template is your index.html.
Calling the state from state
Whe we want in where state navigate to when, we can use directiv ui-sref, but it must contain state name, not view naming convention
// instead of this
<a ui-sref="wizard.what#"
we need this
<a ui-sref="wizard.what"
The reason, that in this three level hierarchy we do use only parent and child names (not grand parent 'home'), is hidden in state definition. Because we used this:
.state("wizard", {
parent: 'home',
Parent is just a parent, not part of the state name. Which is good in scenarios like this (we need the root/grand parent to establish some comon stuff, but it name is not needed for substates)
Check the doc:
ui-sref
A directive that binds a link (<a> tag) to a state. If the state has an associated URL, the directive will automatically generate & update the href attribute via the $state.href() method. Clicking the link will trigger a state transition with optional parameters.
...
You can specify options to pass to $state.go() using the ui-sref-opts attribute. Options are restricted to location, inherit, and reload.
ui-sref - string - 'stateName' can be any valid absolute or relative state
[S]tep one is embedded on the homepage rather than being a separate state
You should treat each ui-view as a state, but declare wizard.where as the default/index state.
Note that the tutorial uses $urlRouterProvider to make form/profile the default state.
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form/profile');
In this manner, however, /form will end up as /form/profile.
You may, however, create an empty URL state with minor modification:
// route to show our basic form (/form)
.state('form', {
url: '/form',
templateUrl: 'form.html',
controller: 'formController',
abstract: true //<-- Declare parent as an abstract state.
})
// nested states
// each of these sections will have their own view
// url will be nested (/form)
.state('form.profile', {
url: '', //<-- Empty string for "profile" state to override the /form abstract state
templateUrl: 'form-profile.html'
})
// catch all route
// send users to the form page
$urlRouterProvider.otherwise('/form'); //<-- Default state is empty
#radim-köhler has also provided great insight into UI-Router and state definitions.
Alright so I am having an issue with my named views loading content into my state.
Its mostly problematic because I really do not know why it isnt working; with angular that usually means a typo but I have recreated my problem in a plunker and am getting the same results so maybe I am missing something.
I know this question has been asked before:: however in all the results I saw on here people were putting content into an abstract state's children. what I want to do is have a state; populate it with named views as well as other content relevant to that state; then have that states children load content into the main states named views. should be simple enough and rather straight forward but alas mine will not work for me.
Here is the link to the plunker made:: http://plnkr.co/edit/TWCQuoIyJRvTb42Z7xxe?p=preview
as you will see the main 'papers' state is loading. however none of the content from the named views is being loaded into the 'papers' state from its child state 'papers.views'.
Code for reference:
Module declaration and state config(app.js)
var app = angular.module( 'app', [ 'ui.router' ] );
app.config(['$stateProvider', '$urlRouterProvider',
function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/papers');
// States
$stateProvider
.state( 'papers', {
url: "/papers",
templateUrl: 'papers.html'
}) // nested paper state + views
.state( 'papers.views', {
views: {
'#papers': {
templateUrl: 'papers.home.html'
},
'paper1#papers': {
templateUrl: 'papers.paper1.html'
},
'paper2#papers': {
templateUrl: 'papers.paper2.html'
}
}
})
}
])
.run(['$rootScope', '$state', '$stateParams',
function ($rootScope, $state, $stateParams) {
$rootScope.$state = $state;
$rootScope.$stateParams = $stateParams;
}])
Index page (papers.html loading here):
<body>
<div ui-view></div>
</body>
papers page ( where nested views are supposed to be loading )
<h1>This is the papers page. other views should load in here</h1>
<div ui-view ></div>
<div ui-view="paper1" ></div>
<div ui-view="paper2" ></div>
One way, how to fix this is to add two lines:
change your parent to be abstract : true and
force child to define url : ''
There is an upated and working plunker, this is the updated state def:
$urlRouterProvider.otherwise('/papers');
// States
$stateProvider
.state( 'papers', {
// NEW LINE
abstract: true,
url: "/papers",
templateUrl: 'papers.html'
}) // nested paper state + views
.state( 'papers.views', {
// NEW LINE - because parent is abstract, same url here - this will be loaded
url: '',
views: {
'#papers': {
templateUrl: 'papers.home.html'
},
'paper1#papers': {
templateUrl: 'papers.paper1.html'
},
'paper2#papers': {
templateUrl: 'papers.paper2.html'
}
}
})
More details about this in documentation:
How to: Set up a default/index child state
Check it here
Another way could be to change the default:
$urlRouterProvider.otherwise('/papers/view');
And add url: '/view' to child state:
...
.state( 'papers.views', {
url: '/view',
views: {
...
Check this version here
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.
Though the states are changing successfully, I can't get the ui-view to update:
HTML
<a ui-sref="promo.foo">Foo state</a>
<a ui-sref="promo.bar">Bar state</a>
<div ui-view=""></div>
JavaScript
$urlRouterProvider.otherwise("/");
$stateProvider
.state('promo', {
url: "/",
abstract: true
})
.state('promo.foo', {
url: "promo/foo",
template: "'<h3>Foo</h3>'",
controller: function($scope) {
$scope.value = 'foo';
}
})
.state('promo.bar', {
url: "promo/bar",
template: "'<h3>Bar</h3>'",
controller: function($scope) {
$scope.value = 'bar';
}
})
Plnkr (with bootstrap styling)
I have also tried setting ui-view to equal specific states also; and to dynamically change its RHS from my $scope.
Since you're trying to create nested views, the home state should have a template with an ui-view-element in it.
Also you have both double and single quotes in the template
template: "'<h3>Foo</h3>'"
Is this Plunker what you had in mind?
Give your abstract state a template containing inside which your sub states will render ... eg http://scotch.io/tutorials/javascript/angular-routing-using-ui-router