How do I show multiple views in an abstract state template? - angularjs

Using Angularjs UI-Router I'm trying to build a typical page with a main view and a side refine/filter results view.
I've forked the sample app and now modifying it to have a parent abstract state that displays the filters view and the main view so that I can share the data that the abstract state resolves.
The problem is I cannot get the filters view to appear without breaking the scope inheritance. If I add the 'views' param it breaks the scope so how can I get this to work?
Here's my code and Plunker
$stateProvider
.state('applications', {
abstract: true,
url: '/applications',
templateUrl: 'planning.html',
controller: function($scope){
$scope.planning = [{ id:0, name: "Alice" }, { id:1, name: "Bob" }];
$scope.filterPlanning = function(data) {
var output = $scope.planning;
// test filter
output = $filter('filter')({ name: "Alice" });
return output;
}
},
/* this breaks the scope from being inherited by the child views
views: {
'': {
templateUrl: 'applications.html'
},
'filters#applications': {
templateUrl: 'applications.filters.html'
}
},*/
onEnter: function(){
console.log("enter applications");
}
})
child states:
.state('applications.list', {
url: '/list',
// loaded into ui-view of parent's template
templateUrl: 'planning.list.html',
onEnter: function(){
console.log("enter applications.list");
}
})
.state('applications.map', {
url: '/map',
// loaded into ui-view of parent's template
templateUrl: 'planning.map.html',
onEnter: function(){
console.log("enter applications.map");
}
})
applications.html
<div class="row">
<div class="span4">
<div ui-view="filters"></div>
</div>
<div class="span8">
<div ui-view></div>
</div>
</div>
I already have the list, map and filters views built as directives so I'm hoping once I can get this demo working I can swap them in relatively easily.

There is updated plunker
I would say, that the issue here is:
Any controller belongs to view not to state
so this is the adjusted code:
.state('applications', {
abstract: true,
url: '/applications',
// templateUrl and controller here are SKIPPED
// not used
// templateUrl: 'applications.html',
// controller: ...
views: {
'': {
templateUrl: 'applications.html',
controller: function($scope, $filter){
$scope.planning = [
{ id:0, name: "Alice" },
{ id:1, name: "Bob" }
];
$scope.filterPlanning = function(data) {
var output = $scope.planning;
// test filter
output = $filter('filter')({ name: "Alice" });
return output;
}
},
},
'filters#applications': {
templateUrl: 'applications.filters.html'
}
},
Check the doc:
Views override state's template properties
If you define a views object, your state's templateUrl, template and templateProvider will be ignored. So in the case that you need a parent layout of these views, you can define an abstract state that contains a template, and a child state under the layout state that contains the 'views' object.
The plunker to check it in action

Related

Can a UI-Router parent state access it's child's members?

I'm using AngularJS's UI-Router to manage routes for my web application.
I have two states: parent_state and child_state arranged as shown below.
$stateProvider
.state('parent_state', {
abstract: true,
views: {
'#' : {
templateUrl: 'http://example.com/parent.html',
controller: 'ParentCtrl'
}
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
views: {
'mainarea#parent_state': {
templateUrl: 'http://example.com/child.html',
controller: 'ChildCtrl'
}
}
})
From within ChildCtrl, I can access myArg like this:
app.controller("ChildCtrl", function($stateParams) {
console.log('myArg = ', $stateParams.myArg);
});
Is it possible for me to access myArg and have it displayed in the html page parent.html? If so, how can it be done? I see that the ParentCtrl controller for the abstract state is never even called.
This question addresses a related topic. But it doesn't show me how to display a parameter to the child state in a template of the parent state.
The first thing that comes to my mind is to use events for notifying parent after child param change. See the following (you can even run it here).
Child, after rendering, emits an event to the parent with the changed value of the parameter. Parent grabs and displays it in its own template.
angular.module('myApp', ['ui.router'])
.config(function ($stateProvider, $urlRouterProvider) {
$stateProvider
.state('parent_state', {
abstract: true,
template: "<h1>Parent! Value from child: {{ paramFromChild }}</h1><div ui-view></div>",
controller: function ($scope) {
$scope.$on('childLoaded', function (e, param) {
$scope.paramFromChild = param;
});
}
})
.state('child_state', {
parent: 'parent_state',
url: '/child',
params: {
myArg: {value: null}
},
template: '<h2>Child! Value: {{ param }}</h2>',
controller: function($stateParams, $scope){
$scope.param = $stateParams.myArg;
$scope.$emit('childLoaded', $stateParams.myArg);
}
});
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.10/angular.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular-ui-router/1.0.20/angular-ui-router.js"></script>
<div ng-app="myApp">
<a ui-sref="child_state({myArg: 'first'})">First link</a>
<a ui-sref="child_state({myArg: 'second'})">First second</a>
<div ui-view></div>
</div>
Is it possible for me to access myArg and have it displayed in the
html page parent.html?
That is against the principle of the UI-Router. Parent params can be consumed in children, but not vice versa. How would parent view know about changes WITHOUT re-initializing the controller? You need something like watching.
The true way is to employ Multiple Named Views. Look at this working plunkr.
Yes, this is possible.
Using $stateChangeSuccess:
You can use $stateChangeSuccess to achieve this.
For example:
.state('main.parent', {
url: '/parent',
controller: 'ParentController',
controllerAs: 'vm',
templateUrl: 'app/parent.html',
data: {
title: 'Parent'
}
})
.state('main.parent.child', {
url: '/child',
controller: 'ChildController',
controllerAs: 'vm',
templateUrl: 'app/child.html'
})
And in the runblock call it as follows:
$rootScope.$on('$stateChangeSuccess', function (event, toState, fromState) {
var current = $state.$current;
if (current.data.hasOwnProperty('title')) {
$rootScope.title = current.data.title;
} else if(current.parent && current.parent.data.hasOwnProperty('title')) {
$rootScope.title = current.parent.data.title;
} else {
$rootScope.title = null;
}
});
Then you can access the $rootScope.title from the child controller since it is globally available.
Using a Factory or Service:
By writing setters and getters you can pass data between controllers. So, you can set the data from the child controller and get the data from the parent controller.
'use strict';
(function () {
var storeService = function () {
//Getters and Setters to keep the values in session
var headInfo = [];
return {
setData: function (key, data) {
headInfo[key] = data;
},
getData: function (key) {
return headInfo[key];
}
};
};
angular.module('MyApp')
.factory('StoreService', storeService);
})(angular);
Set data from child controller
StoreService.setData('title', $scope.title)
Get data
StoreService.getData('title');
Using events $emit, $on:
You can emit the scope value from the child controller and listen for it in the parent scope.

AngularJS router-ui. Load child state in named view on the same level as parent

What i'm trying to do:
<div id="chat">
<div ui-view>Here should people.htm be loaded</div>
<div ui-view="chat">Here is current person chat peopleChat.htm</div>
</div>
I already managed a nested structure. If "chat" is child of "people" - no problem.
But I want em to remain on the same level, but be in a different state. Something like.
$stateProvider
.state('people', {
url: '/people',
templateUrl: ...,
controller: ...
})
.state('people.chat', {
views: {
'chat': {
url: '/:personId',
templateUrl: ...,
controller: ...
}
}
})
My unnamed view is filling with data. After unnamed view is filling, i'm calling $state.go('people.chat', { personId: vm.personId });
But nothing is happening.
Name both views and you are ok:
<div id="chat">
<div ui-view="main">Here should people.htm be loaded</div>
<div ui-view="chat">Here is current person chat peopleChat.htm</div>
</div>
And your controler:
$stateProvider
.state('people', {
views: {
'main#': {
url: '/people',
templateUrl: ...,
controller: ...
}
}
})
.state('people.chat', {
views: {
'chat#': {
url: '/:personId',
templateUrl: ...,
controller: ...
}
}
})
Basically the # absolute targets the view.
Meaning if you use it like chat# it targets the named view chat in the root html.
If you want to nest the views you can use chat#people
which targets the ui-view loaded in the template that people state has injected.
Plunker

Angular ui-router passing data between subviews of the same state

How do I access other subviews in the same state. I am building a page with a toolbar on top and a sidebar and I want a button on the toolbar to open/close the sidebar and buttons in the sidebar to change the content. easy of it's all in the same controller but what I did is to use ui-router's subview feature like this:
.state('dash', {
url: '/dash/:id',
views: {
nav: {
controller: 'NavCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/navbar.html'
},
sidebar: {
controller: 'SidebarCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/sidebar.html'
},
content: {
controller: 'DashCtrl',
controllerAs: 'ctrl',
templateUrl: '/views/dash.html'
}
}
})
UI looks like this:
Define a resolve and use it as a place to store common data for the activated 'dash' state.
app.config(function($stateProvider) {
$stateProvider.state('dash', {
url: '/',
resolve: {
dashData: function() {
return { input: "default value" };
}
},
views: {
nav: {
controller: function() {
},
controllerAs: 'ctrl',
template: '<h3>This is the Navbar</h3>'
},
sidebar: {
controller: function(dashData) { // Inject reference to resolve object
this.dashData = dashData;
},
controllerAs: 'ctrl',
template: 'content data visible in ' +
'the sidebar: <b>{{ ctrl.dashData.input }}<b>'
},
content: {
controller: function(dashData) { // Inject reference to resolve object
this.dashData = dashData;
},
controllerAs: 'ctrl',
template: '<input type="text" ng-model="ctrl.dashData.input">' +
'This is bound to dashData.input'
}
}
})
});
Inject the shared object into each controller
app.controller('DashCtrl', function(dashData, $scope) {
$scope.dashData = dashData;
});
app.controller('... ....
I put this example in a plunker for you: http://plnkr.co/edit/8M1zXN0W5ybiB8KyxvqW?p=preview
This would be a good example of where an abstract parent state comes in handy:
An abstract state can have child states but can not get activated itself. An 'abstract' state is simply a state that can't be transitioned to. It is activated implicitly when one of its descendants are activated.
https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views#abstract-states
And then especially this usecase:
inherit $scope objects down to children
Consider the following abstract parent state and it's child state:
$stateProvider.state('root', {
abstract: true,
url: '/dash',
templateUrl: 'root.html',
controller: 'rootController'
});
$stateProvider.state('dash', {
parent: 'root',
url: '/:id',
views: {
'navbar': {
templateUrl: 'navbar.html',
controller: 'navbarController'
},
'sidebar': {
templateUrl: 'sidebar.html',
controller: 'sidebarController'
},
'content': {
templateUrl: 'content.html',
controller: 'contentController'
}
}
});
Now you can store logic (and data) you need in your childstate in the controller of the abstract parent state:
angular.module('app').controller('rootController', [
'$scope',
function ($scope) {
$scope.sidebar = {
show: true
};
$scope.items = [{
name: 'Alpha'
}, {
name: 'Bravo'
},{
name: 'Charlie'
},{
name: 'Delta'
}];
$scope.selected = $scope.items[0];
$scope.select = function (item) {
$scope.selected = item;
}
}
]);
Example of using this logic/data in a template of the child state, sidebar.html:
<ul class="nav nav-pills nav-stacked">
<li ng-repeat="item in items" role="presentation">
{{item.name}}
</li>
</ul>
Here's a complete example with your requirements, i could post all the code here but i think that would be a bit too much:
http://embed.plnkr.co/2jKJtFM0GWsylyLcAdne/
I'll gladly answer any question you may have, just holler. Good luck!
if you'd name your controllers differently with controller as, you could use the NavCtrl in the sidebarCtrl's template. Maybe use some boolean value that exists on the NavCtrl, that decides what to show in the sidebar? (from the comment)
This should work, haven't tried it though.
.state('dash', {
url: '/dash/:id',
views: {
nav: {
controller: 'NavCtrl',
controllerAs: 'navCtrl',
templateUrl: '/views/navbar.html'
},
sidebar: {
controller: 'SidebarCtrl',
controllerAs: 'sidebarCtrl',
templateUrl: '/views/sidebar.html'
},
content: {
controller: 'DashCtrl',
controllerAs: 'dashCtrl',
templateUrl: '/views/dash.html'
}
}
})
sidebarService:
angular.module('app').value('sidebarService', {show: true});
navCtrl something like this:
function(sidebarService){
var vm = this;
vm.toggleSideBar = function(){sidebarService.show = !sidebarService.show;}//used in navbar.html
}
sidebarCtrl:
function(sidebarService){
var vm = this;
vm.showSideBar= sidebarService;
}
and then in sidebar.html you use the sidebar value service:
<div ng-if="sidebarCtrl.showSideBar.show">
<!--SideBar-->
</div
You can use events to communicate between controllers. Check the AngularJS documentation for $scope.$broadcast and `$scope.$on : https://docs.angularjs.org/api/ng/type/$rootScope.Scope

Using UI-Router - Is it possible to set only single ui-view using regular syntax?

In one of my templates , I have this code :
<h1>Route 1</h1>
<hr/>
<a ui-sref=".list">Show List</a>
<a ui-sref=".thing">thing7</a>
<div ui-view="left"></div>
<div ui-view="right"></div>
When I click the .list state - I do have a controller with the appropriate configuration :
....state('route1.list',
{
url: '/list',
views:
{
'left#route1':
{
templateUrl: 'route1.list.html',
controllerAs: "vm",
controller: function ($scope)
{
this.items = ["a", "b", "c", "d"];
}
},
'right#route1':
{
templateUrl: 'route1.list.html',
controllerAs: "vm",
controller: function ($scope)
{
this.items = ["1", "2", "3", "4"];
}
}
}
})
This is working fine and I do see both views getting filled with appropriate data.
Now - when I click on the .thing state - I want only this view :
<div ui-view="right"></div>
To be loaded with data.
So obviously I should use this code :
.state('route1.thing',
{
url: "/thing",
views:
{
'right#route1':
{
templateUrl: 'route3.html',
controllerAs: "vm",
controller: function($scope)
{
this.thing = "I'm aaa";
}
}
}
})
Question
When I have multiple view on the page , and I only want to touch a single view (not the others) — Must I still using this structure :
.state('route1.thing',
{
url: "/thing",
views:
{
...
}
I mean - Is there anything which allows me to apply the info into a specific view like in the regular format:
.state('route1.thing', {
url: "/thing",
templateUrl: "route3.html",
applyTo:"right#route1" , // <-- Something like this
controller: function($scope){
....
}
})
What we can do with UI-Router in this case, is file nesting...
Based on the plunker created by Royi Namir, I made few adjustment and there is an updated and working plunker
So, we have parent state 'route1.list':
.state('route1.list',
{
url: '/list',
views:
{
'left#route1':
{
...
},
'right#route1':
{
...
}
}
})
And we want to change just left or right part. As already said, we can do that with state nesting - but nesting anther the above state 'route1.list' (not under the grand parent 'route1')
.state('route1.list.thing', {
url: '/thing',
views: {
'right#route1': {
...
}
}
})
.state('route1.list.left', {
url: '/left',
views: {
'left#route1': {
...
}
}
})
And these links will change stuff as expected:
<a ui-sref=".list">
<a ui-sref=".list.left">this will change the left</a>
<a ui-sref=".list.thing">this will change the right</a>
Check it here

How to hide content in the parent view when the user goes to a nested state and show it again when the user backs to the parent one?

I use ui-router and have two nested views.
I’d like to hide some html-content in the parent view when the user goes to child state and show it again when the user backs to parent one.
Could anybody give an advice how to achieve that?
It’s easy to do that using root scope and state change events but it looks like a dirty way, doesn’t it?
app.js
'use strict';
var myApp = angular.module('myApp', ['ui.router']);
myApp.controller('ParentCtrl', function ($scope) {
$scope.hideIt = false;
});
myApp.controller('ChildCtrl', function ( $scope) {
$scope.$parent.hideIt = true;
});
myApp.config(function ($stateProvider, $urlRouterProvider) {
$urlRouterProvider.otherwise('/');
$stateProvider
.state('parent', {
url: '/parent',
templateUrl: 'parent.html',
controller: 'ParentCtrl'
})
.state('parent.child', {
url: '/child',
template: '<h2>Child view</h2>',
controller: 'ChildCtrl'
});
});
parent.html
<h2>Parent view</h2>
<div ng-hide="hideIt">
<p>This text should be hidden when the user goes to the nested state.</p>
</div>
<a ui-sref="parent.child">Go to the nested view</a>
<div ui-view></div>
One simple solution is to fill ui-view tag in the parent template with the content that you want gone when child state is loaded.
<ui-view>
<h2>Parent view</h2>
<div ng-hide="hideIt">
<p>This text should be hidden when the user goes to the nested state.</p>
<a ui-sref="parent.child">Go to the nested view</a>
</ui-view>
You should check out named views for this. That would probably be the best way to go. https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
Also, there's another thread that answered this question over here: https://stackoverflow.com/a/19050828/1078450
Here's the working code for nested named views, taken from that thread:
angular.module('myApp', ['ui.state'])
.config(['$stateProvider', '$routeProvider',
function($stateProvider, $routeProvider) {
$stateProvider
.state('test', {
abstract: true,
url: '/test',
views: {
'main': {
template: '<h1>Hello!!!</h1>' +
'<div ui-view="view1"></div>' +
'<div ui-view="view2"></div>'
}
}
})
.state('test.subs', {
url: '',
views: {
'view1#test': {
template: "Im View1"
},
'view2#test': {
template: "Im View2"
}
}
});
}
])
.run(['$state', function($state) {
$state.transitionTo('test.subs');
}]);
http://jsfiddle.net/thardy/eD3MU/
Edit:
Adding some thoughts re the angular-breadcrumbs comment. I haven't used it myself, but at first glance it seems like subroutes shouldn't break the breadcrumbs. I'm just looking at the source code of their demo, around line 62. I'd have to spin it all up to really go about testing it, but it looks like they're doing almost the same thing with specifying views, and it works there: https://github.com/ncuillery/angular-breadcrumb/blob/master/sample/app.js#L62
.state('room', {
url: '/room',
templateUrl: 'views/room_list.html',
controller: 'RoomListCtrl',
ncyBreadcrumb: {
label: 'Rooms',
parent: 'sample'
}
})
.state('room.new', {
url: '/new',
views: {
"#" : {
templateUrl: 'views/room_form.html',
controller: 'RoomDetailCtrl'
}
},
ncyBreadcrumb: {
label: 'New room'
}
})
.state('room.detail', {
url: '/{roomId}?from',
views: {
"#" : {
templateUrl: 'views/room_detail.html',
controller: 'RoomDetailCtrl'
}
},
ncyBreadcrumb: {
label: 'Room {{room.roomNumber}}',
parent: function ($scope) {
return $scope.from || 'room';
}
}
})
Edit2:
This solution will not combine routes into one crumb
See the official demo
re: But I use angular-breadcrumb and in your solution they will be combined into one crum.

Resources