I've got a nested view ui-router application, whenever I access the child with controller directly, the parent data is not filled (but is called, and I can see logs)
what is wrong?
.state('home.app', {
url: '/apps/:package_name',
abstract: true,
templateUrl: 'app/components/apps/details/app_header.html',
controller: 'AppController',
controllerAs: 'appCtrl'
})
.state('home.app.details', {
url: '/', templateUrl: 'app/components/apps/details/app.html'
})
.state('home.app.images', {
url: '/images', templateUrl: 'app/components/apps/details/images.html',
controller: 'ImagesController',
controllerAs: 'imagesCtrl',
resolve: {
package_name: function($stateParams: IStateParamsService) {
return $stateParams.package_name;
}
}
})
when I access the default child (details child) the parent loads and everything is ok (view is filled with data) but no luck with images, the AppController is being called but the view is not filled
if I'm not allowed to have such structure, what is my alternative? I have some shared data, and some specific data that should load on child states (tho I can load them all in parent controller but I guess that is not the way to go, since parent controller should not load submodels)
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
I have this in my app.js:
$stateProvider
.state('actionplans', {
url: "/actionplans",
templateUrl: "pages/actionplans.html",
//controller : 'ActionplansCtrl'
})
.state('actionplans.planning', {
url: "/planning",
templateUrl: "pages/actionplans.planning.html",
//controller : 'ActionplansCtrl'
})
.state('actionplans.summary', {
url: "/summary",
templateUrl: "pages/actionplans.summary.html",
//controller : 'ActionplansCtrl'
})
How can I default load nest view action 'actionplans.summary.html' when called actionplans.html?
There is a working example
The way which will
load some view inside of a parent - and stay on parent
allow child change it when navigating to child
is called Multiple named views:
.state('actionplans', {
url: "/actionplans",
views: {
'': {
templateUrl: "pages/actionplans.html",
//controller : 'ActionplansCtrl'
},
'#actionplans': {
templateUrl: "pages/actionplans.summary.html",
//controller : 'ActionplansCtrl'
}
}
})
.state('actionplans.planning', {
url: "/planning",
templateUrl: "pages/actionplans.planning.html",
//controller : 'ActionplansCtrl'
})
.state('actionplans.summary', {
url: "/summary",
templateUrl: "pages/actionplans.summary.html",
//controller : 'ActionplansCtrl'
})
What we did above, is that we used views : {} object to define two views. First is targeting the index.html (the '') the second is targeting this state view target for children ( the '#actionplans').
views: {
'': { // index.html
...
},
'#actionplans': { // this targets the unnamed view for children
Read more about absolute names here
Another way, is to define some default redirection, but that will disable parent state as a real target (e.g. here Redirect a state to default substate with UI-Router in AngularJS)
Here discuss about AngularJS Routing Using UI-Router, you will get enough idea about nested view and multiple view.
https://scotch.io/tutorials/angular-routing-using-ui-router
I found a simple solution here.
$urlRouterProvider.when('/actionplans', '/actionplans/summary');//<-- Add in this line
$stateProvider
.state('actionplans', {
url: "/actionplans",
abstract: true,/// <-- Add in this line
templateUrl: "pages/actionplans.html",
})
.state('actionplans.planning', {
url: "/planning",
templateUrl: "pages/actionplans.planning.html",
})
.state('actionplans.summary', {
url: "/summary",
templateUrl: "pages/actionplans.summary.html",
})
This will load nest view actionplans.summary.html by default when you call /actionplans. My apology that I did not make this clearer in my question so I post the answer here hopefully it will help someone else with the similar scenario.
I have a page showing the list of applications that I want to be able to go to the page of the details of the app, when I click on each one of them.
Here is my config:
module bandar {
'use strict';
export class RouterConfig {
/** #ngInject */
constructor($stateProvider: ng.ui.IStateProvider,
$urlRouterProvider: ng.ui.IUrlRouterProvider,
$locationProvider: ng.ILocationProvider) {
$stateProvider
.state('home', {
url: '',
abstract: true,
templateUrl: 'app/components/main/main.html',
controller: 'MainController',
controllerAs: 'mainCtrl'
})
.state('home.apps', {
url: '/apps',
abstract: true,
templateUrl: 'app/components/apps/apps.html',
controller: 'AppsController',
controllerAs: 'appsCtrl',
})
.state('home.apps.list', {
url: '',
templateUrl: 'app/components/apps/list.html',
})
.state('home.app.detail', {
url: '/app/:package_name',
templateUrl: 'app/components/apps/app.html',
controller: 'AppController',
controllerAs: 'appCtrl',
});
$urlRouterProvider.otherwise('/apps');
/*$locationProvider.html5Mode(true).hashPrefix('');*/
}
}
}
And here is the part of the list template which is anchoring to the app's details page:
<a ui-sref="home.app.detail({package_name: app.package_name})">{{app.title}}</a>
But when I hit it in my browser, the following error occurs in the console:
Error: Could not resolve 'home.app.detail' from state 'home.apps.list'
at Object.transitionTo (angular-ui-router.js:3140)
at Object.go (angular-ui-router.js:3068)
at angular-ui-router.js:4181
at angular.js:17682
at completeOutstandingRequest (angular.js:5387)
at angular.js:5659
I guess the problem is UI-Router thinks that I'm pointing at the state relatively, but I wanna do it in the absolute way.
The problem is parent name 'home.app' instead of 'home.apps'
// wrong
.state('home.app.detail', { ...
// should be
.state('home.apps.detail', { ...
because parent is
.state('home.apps', { ...
EXTEND in case, that this should not be child of 'home.apps' we have to options
1) do not inherit at all
.state('detail', { ...
2) introduce the parent(s) which is(are) used in the dot-state-name-notation
// exists already
.state('home', { ...
// this parent must be declared to be used later
.state('home.app', {
// now we can use parent 'home.app' because it exists
.state('home.app.detail', {
I just started migrating to AngularJS and I'm already having problems with the $stateProvider from the angular-ui/ui-router framework. This is what I have so far:
angular
.module('testApp', ['ui.router'])
.config(function($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise("/index/home");
$stateProvider
.state('index', {
abstract: true,
url: "/index",
template: "parent"
})
.state('index.home', {
url: "/home",
template: "child"
})
})
.controller("MainController", function() {
})
Now, running this script redirects me to http://example.com/#/index/home but it only displays the parent string on the page. The child string is not being shown. From my understanding this should load the first template because we are on the /#/index domain part and then the second because we are on a nested page /#/index/home.
Can someone help me and explain why this is not working as intended?
In your template for the parent you need another <div ui-view></div> in order to render child states.
If you want multiple nested views, you can have multiple ui-view in your parent template. You just have to name them. For example,
parent template:
<h1>parent</h1>
<div ui-view="child1"></div>
<div ui-view="child2"></div>
And you define the child states as follows:
$stateProvider
.state('index', {
abstract: true,
url: "/index",
template: "parent"
})
.state('index.home', {
url: "/home",
views: {
'child1': {
templateURL: "child1.html"
}
}
})
.state('index.home2', {
url: '/home2',
views: {
'child2': {
templateURL: 'child2.html'
}
}
})
*note I used templateURL instead of template. Assuming your app has modular file structure.
I have a state that has multiple views declared in it as follows:
$stateProvider
.state('home.details.item', {
url: '^/details',
views: {
'chartsView': {
templateUrl: 'charts.html',
controller: 'chartsCtrl'
},
'gridView': {
templateUrl: 'grid.html',
controller: 'gridCtrl'
},
'detailsView': {
templateUrl: 'details.html',
controller: 'detailsCtrl'
}
}
});
I need to reload one of the views without reloading the whole state, without using $state.go($state.current,null , {reload: true}) , and if possible, from the chartCtrl reload detailsCtrl. Is that possible?
I'd say, that the UI-Router solution should be built arround *states*, not views.
(I created working example here). Other words, if there are
some views which should not be reloaded and
some other views, which should be reloaded
... it calls for state nesting. Let's move that view into child state:
.state('home.details.item', {
url: '^/details',
views: {
'chartsView': {
templateUrl: 'tpl.charts.html',
controller: 'chartsCtrl'
},
'gridView': {
templateUrl: 'tpl.grid.html',
controller: 'gridCtrl'
},
// 'detailsView': {
// templateUrl: 'details.html',
// controller: 'detailsCtrl'
// }
}
})
.state('home.details.item.more', {
views: {
'detailsView#home.details': {
templateUrl: 'tpl.details.html',
controller: 'detailsCtrl'
}
}
})
We also need a state, which will do the reload. We could use other way, e.g. with some changing parameter in state more, but that would mean to change the param value on each call. With this specil state, we can easily reload our state 'more':
.state('reload', {
parent: "home.details.item",
views: {
'detailsView#home.details': {
// this controller will just redirect to 'more' and make it fresh...
controller: ['$state', function($state) { $state.go('^.more')}],
}
}
})
And with these simple controllers we can do all that required stuff:
.controller('chartsCtrl', function ($scope, $state) {
var childName = ".more";
$state.go(childName); // default is a sub state 'more'
})
.controller('detailsCtrl', function ($scope) {
$scope.when = Date.now();
})
Having this: we can call this to reload just details:
<a ui-sref="reload">force reload detail view</a>
Now, when navigating to reload, we will be redirected to state "more" and our view will be rerendered.
SUMMARY:
In general, UI-Router represents state machine. I would strongly suggest:
Do not worry to think in states. Views are just their representation in the DOM.
If there are some features related, they most likely represent state. If others do not relate (should be changed often or rarely) they belong to other state. It could be parent, child or sibling...
Check it here