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
Related
I'm having bad time with nested state, resolve and browser back button.
Below my states configuration, it consists of one abstract state and 2 children state, one to view a profile,
one to reserve it. The usual flow is opening the profile page, then click on some link and open the request page; this works fine. However if I click the browser back button on profile-request page, ProfileViewController is executed but profileData is not resolved.
Is there a way either to reload the previous view as it was (without having to rendering-it again) or force the promise resolution before executing the controller?
$stateProvider
.state('public', {
abstract: true,
templateUrl: 'app/public.html'
})
.state('public.profile-view', {
url: '/profile/:slug',
templateUrl: 'app/profile/profile.view.html',
controller: 'ProfileViewController',
controllerAs : 'vm',
resolve: {
profileData: function($stateParams, Profiles) {
return Profiles.query({ slug: $stateParams.slug });
}
}
})
.state('public.profile-request', {
url: '/profile/request/:slug',
templateUrl: 'app/profile/profile.request.html',
controllerAs: 'vm',
controller: 'ProfileRequestController',
resolve: {
profileData: function($stateParams, Profiles) {
return Profiles.query({ slug: $stateParams.slug });
}
}
})
Have you tried caching the profileData in Profiles and then in ProfileViewController try to get cached data if it is existing.
Another option is storing profileData in the $rootScope.
I have been reading about nested views and multiple views but I can't find an example using both
In a landing page I have multiple views, one after other. A picture speaks a thousand words:
To consider:
Every section/view will have full window height, so on scroll I want to change location to /view1, /view2, /view3, etc.
That should be compatible with going to /view1/b or /view3/b and showing subview (view1.b or view3.b).
Scroll should not make load page again.
I have success doing tasks separately but not all together.
you can use $statePorivder nested view with
config(function ($stateProvider) {
$stateProvider
.state('main', {
url: '/',
views: {
'': {
templateUrl: 'app/main/layout.html',
controller: 'LayoutController'
},
'header#main': {
templateUrl: 'app/main/header/layout.html',
controller: require("./headerController.js")
},
'left#main': {
templateUrl: 'app/main/left/layout.html',
controller: require("./leftController.js")
},
'content#main': {
templateUrl: 'app/main/content/layout.html',
controller: require("./conentController.js")
},
'right#main': {
templateUrl: 'app/main/right/layout.html',
controller: require("./rightController.js")
}
}
});
};
I am using Ionic and Angular 1 (with ui-router). Let's say I have 2 tabs:
+----+ +-------+
|HOME| |PROFILE|
+----+ +-------+
This is the important part: there's a link within the HOME tab to /profile... but, I do not want it to automatically switch to the PROFILE tab when I click that link... I want to continue with the navigation stack within the HOME tab.
The usual approach using ui-router to have multiple nested states is something like this (some unnecessary things for this example purposefully omitted):
$stateProvider
.state('app', {
abstract: true,
template: require('templates/app.html'),
})
.state('app.home', {
template: require('templates/home.html'),
url: '/home',
controller: 'HomeCtrl',
views: {
// a bunch of child views that can only belong to this state
}
})
.state('app.profile', {
template: require('templates/profile.html'),
url: '/profile',
controller: 'ProfileCtrl',
views: {
// a bunch of child views that can only belong to this state
}
});
But what if I want to navigate to app.profile inside of app.home, instead of being forced to change tabs?
One options is to make all my tabs abstract and then declare <tab>.<state> for every possible state (using a loop), but this seems like it requires a lot of possibly unnecessary overhead:
var abstractStates = [
{
name: 'home',
url: '/home',
controller: 'HomeCtrl',
template: require('templates/home.html')
},
{
name: 'profile',
url: '/profile',
controller: 'ProfileCtrl',
template: require('templates/profile.html')
}
];
abstractStates.forEach(function(state) {
$stateProvider.state(state.name, _.omit(state, ['name']);
abstractStates.forEach(function(nestedState) {
$stateProvider.state([state.name, nestedState.name].join('.'), _.omit(nestedStates, ['abstract', 'name']));
});
});
Is there a better way to accomplish this?
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.
I have few common views (like breadcrumbs, notifications, sidebar etc) defined in an abstract state and few other child states inheriting them. I want to modify a view in the parent (ie: adding a new menu item to sidebar view) when inside a child state. I think I can use view-name#parent-state to refer to that view, but not sure how to get it to work. If it's not possible, am I trying to misuse the inheritance behavior in UI-Router? What's the correct way to implement such functionality in Angular/UI-Router? Can I do this even without using UI-Router?
Here's what I was trying to do:
$stateProvider
.state('posts', {
url: '/posts',
abstract: true,
views: {
"main": { template:'<div ui-view="main-content"></div>'},
"breadcrumbs": { templateUrl: 'breadcrumb.html' },
"navigation": { templateUrl: 'navigation.html' },
"notifications": { templateUrl: 'notifs.html' }
}
})
.state('posts.index', {
url: '',
views: {
"navigation#posts": { templateUrl: 'breadcrumb_mod.html' },
"main-content": { templateUrl: 'partial-config.html' }
}
});