Angular-UI Router: Nested Views Not Working - javascript

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.

Related

Multiple views to nested state

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.

UI-Router abstract state child doesnt render

I'm having some difficulties to understand how UI-Router abstract state work.
Here's the thing :
I have an index.html with only a <ui-view> tag inside its <body>.
There is two main states : Landing and App ( /landing and /app ).
No problem with the landing state for now, because its only a static file ( no child view etc ).
But for my /app state, I need an abstract parent state that cover the whole application ( need to resolve User Profile for each child states ).
The thing is that child of this abstract state also have sub-view. And I can't make those render.
My $stateProvider config (simplified) :
//landing state, no problem here
.state('landing', {
url: '/landing',
templateUrl: 'landing.html'
})
// my abstract parent state, with the resolve
.state('root', {
url: '',
abstract: true,
template: "<div ui-view></div>",
resolve: {
current_user: function (UserFactory) {
return UserFactory.initCurrentUserProfile();
}
}
})
// the child which cannot render the view headerAndSearchbar
.state('root.app', {
url: '/app',
templateUrl: 'app.html',
views: {
'headerAndSearchbar': {
templateUrl: './partials/header/headerAndSearchbar.html'
}
}
})
app.html :
<div ui-view="headerAndSearchbar">
</div>
<div>
This is app.html
</div>
Note that if i remove views declaration in the state root.app, i can see the text "This is app.html".
headerAndSearchbar.html only contains simple html & css
Any ideas ? I'm bashing my head on this- What am I missing?
There are two issues.
Firstly, There are in fact two views, inside of the child 'root.app'.
Unnamed (the one, which is related to templateUrl 'app.html')
named view 'headerAndSearchbar'
And that means, that both most be declared inside of the views : {} setting:
.state('root.app', {
url: '/app',
//templateUrl: 'app.html',
views: {
'' : { templateUrl: 'app.html' },
'headerAndSearchbar': { ... n
}...
But that is not enough.
Secondly, we need absolute naming for the second (named) because it is not related to its parent, but to itself:
.state('root.app', {
url: '/app',
//templateUrl: 'app.html',
views: {
'' : { templateUrl: 'app.html' },
// this is the same as a path to parent view 'headerAndSearchbar#root'
// 'headerAndSearchbar': { ...
// this means ... search in this state, inside of app.html
'headerAndSearchbar#root.app': { ...
}...
Check these for more details:
Angularjs ui-router not reaching child controller
Angular UI Router - Nested States with multiple layouts

Angular UI-Router Nested View Not Displaying Page

I have implemented UI-Router in my application but I'm having a problem with a nested route. My index.html page looks like;
<body>
<!-- Navigation code -->
<!-- Content from the template -->
<div ui-view></div>
<!-- Footer -->
<!-- Application Scripts -->
</body>
My Angular ui-route code is;
(function () {
'use strict';
var app = angular.module('rbApp', ['ngAnimate', 'ui.router', 'ui.bootstrap']);
app.config(["$stateProvider", "$urlRouterProvider", function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/");
$stateProvider
.state("home", {
url: "/",
templateUrl: "/App/WebSite/home.html",
controller: "homeController as homeVm"
})
.state("programs", {
url: "/programs",
templateUrl: "/App/WebSite/programs.html",
controller: "programsController as programVm"
})
.state("programs.complete", {
url: "/complete",
templateUrl: "/App/WebSite/programsComplete.html"
})
.state("articles", {
url: "/articles",
templateUrl: "/App/WebSite/articles.html",
controller: "articleController as articleVm"
})
}]);
app.run(['$rootScope', function ($rootScope) {
$rootScope.$on('$locationChangeStart', function (event, newUrl, oldUrl) {
console.log('Location change: %s --> %s', oldUrl, newUrl);
});
}]);
})();
When I select the "programs" route the page is displayed correctly and the "app.run" code above updates the console log with;
Location change: http://localhost:50321/#/ --> http://localhost:50321/#/programs
On the programs page I have an image within an anchor tag as follows;
<div class="col-md-3 hidden-sm hidden-xs">
<a ui-sref="programs.complete"><img class="img-thumbnail img-responsive" src="/Images/Programs/Program01Small.jpg" /></a>
</div>
When the image is clicked the console log displays;
Location change: http://localhost:50321/#/programs --> http://localhost:50321/#/programs/complete
So it appears the routes are behaving as expected. The only problem is the "programsComplete.html" template is not displayed, the browser continues to display the "programs.html" template.
I have checked the console log but no errors are displayed and currently the "programsComplete.html" only contains a <h1> tag and text so I can confirm the template is being loaded.
Can anyone advise why this would not be loading the template?
It seems that the parent template programs.html is just missing for its child. Be sure that this tempalte programs.html contains unnamed view target
<div ui-view="">
Each child is inserted into its parent. In case we want child to replace parent, we have to use absolute name, and in this case target the index.html like this:
.state("programs.complete", {
url: "/complete",
views : {
'#' : {
templateUrl: "/App/WebSite/programsComplete.html"
}
}
})
while this naming convention could be weird: views : { '#' : ..., it is just absolute name:
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.
so '#' means unnamed view in the unnamed state === root state

AngularJS - Loading named views within child states ( not abstract )

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

Angular ui-router nested view

Im trying to inject a nested view into my normal view, using ui-router.
I know it's possible by doing ng-include. But I want to solve it by using ui-router.
My html is as follow:
<div class="row">
<div class="col-md-12">
<div class="panel panel-default">
<div class="panel-heading">Project todos</div>
<div class="panel-body">
<div ui-view="todos"></div>
</div>
</div>
</div>
</div>
Then in my script I got a state:
.state('project', {
url: '/project/:projectId',
templateUrl: 'views/project/project.html',
controller: 'ProjectCtrl',
views: {
'todos': {
templateUrl: 'views/project/todos.html'
}
}
})
Update - isn't there something like this possible?
.state('project', {
url: '/project/:projectId',
templateUrl: 'views/project/project.html',
controller: 'ProjectCtrl',
views: {
'todos#project': {
templateUrl: 'views/project/todos.html'
}
}
})
Anyone can find a typo or something? I've read the docs. I am not sure what's wrong.
Thanks in advance!
There is a working plunker, showing how we can make it running
On the index.htm we need to have the <div ui-view="" ></div>, which is the place were we inject the project.html. Then we adjust the state definition, to inject also nested view - using ui-view absolute naming:
.state('project', {
url: '/project/:projectId',
views: {
'' : {
templateUrl: 'views.project.project.html',
controller: 'ProjectCtrl',
},
'todos#project': {
templateUrl: 'views.project.todos.html'
}
}
});
The absolute name todos#project, will inject the todos.html into the project.html. Check the plunker
See the:
View Names - Relative vs. Absolute Names
A cite:
...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...

Categories

Resources