Walking by nested states in ui-router without reloading parent state - angularjs

In my project many organizations. Each organization page contains two parts: header with some organization info, which must load only once, and tabs zone. For tabs i use nested states.
Now question: Can i walk by tabs without reloading organizationCtrl?
router.js
$stateProvider
.state('organization', {
url: '/organisations/:id',
templateUrl: 'organization/show.html',
controller: 'organizationCtrl'
})
.state('organization.bio', {
url: '/bio',
templateUrl: 'organization/bio.html',
controller: 'organizationBioCtrl'
})
.state('organization.bio', {
url: '/gallery',
templateUrl: 'organization/gallery.html',
controller: 'organizationGalleryCtrl'
});
organization/show.html
<ul>
<li ui-sref-active="selected">
<a ui-sref="organization.bio({ id: organization.id })">Bio</a>
</li>
<li ui-sref-active="selected">
<a ui-sref="organization.gallery({ id: organization.id })">Gallery</a>
</li>
</ul>

The state change is triggered even if some state-Param is changed. Therefore, until our user will navigate between bio and gallery of the same organisation {id} - the organizationCtrl won't be reloaded. In the moment when user will navigate to different organisation {id} ... new state change will be triggered and new organiaztionCtrl will be put in play.
But we can solve it. Here is a working plunker
We will split the organisation state into 2 (parent/child). One will be a base/root of the organisation without the dependency upon concrete organisation {id}. And that means - will be instantiated only once...
.state('base', {
abstract : true,
template: '<div ui-view=""></div>',
controller: 'baseCtrl'
})
.state('organization', {
parent : 'base',
url: '/organisations/:id',
controller: 'organizationCtrl'
...
})
.state('organization.bio', {
url: '/bio',
...
})
.state('organization.gallery', {
url: '/gallery',
...
});
And now navigating among these links:
<ul>
<li><a ui-sref="organization.bio( { id: 1 })">Orga1 - Bio</a></li>
<li><a ui-sref="organization.gallery({ id: 1 })">Orga1 - Gallery</a></li>
<li><a ui-sref="organization.bio( { id: 2 })">Orga2 - Bio</a></li>
<li><a ui-sref="organization.gallery({ id: 2 })">Orga2 - Gallery</a></li>
</ul>
will not resinstantiate the baseCtrl even if the {id} will be changed.
.controller('baseCtrl', function($scope,...) {
// pretty stable one
})
.controller('organizationCtrl', function($scope, ...) {
// changing with id change
})
Because the base state is abstract, it won't introduce anything into our current model, we only gain the baseCtrl...
Please, observe described behaviour in this working plunker

Related

Angular ui-router subview issues

I've been working with Angular for a year or 2 now, but this is my first project using ui-router. I'm running into a few issues with views and sub-views. The app is a standard left-side menu bar, with the views on the right changing depending on what's clicked in the menu bar.
On index.html
<body>
<div ui-view></div>
</body>
In the config.js file, which defines the routes
.state("dashboard", {
url: "/dashboard",
templateUrl: "components/dashboard/dashboard.html",
data: {
pageTitle: "Dashboard",
requiresLogin: false
}
})
.state("dashboard.welcome", {
url: "/welcome",
templateUrl: "components/welcome/welcome.html",
data: {
pageTitle: "Welcome",
requiresLogin: false
}
})
In the dashboard.html file
<div class="dashboard">
<div class="container-fluid">
<div class="row">
<div class="col-xs-12 col-md-8">
<div ui-view>
The /dashboard path loads correctly, and will load the left-side navigation bar with a blank right side. But changing the state to dashboard.welcome (/welcome) will not load the welcome.html template.
Whenever working with ui-router you need to understand that the concept of states is different from routes. When you define a sub-state, its defined relative to its parent state. In your scenario dashboard.welcome is defined as a child state of dashboard. The routes to substate is relative to the parent and is {parent url}/{child url}. Hence you should use either of the below 2 to route to that state:
Using $state.go change the state by specifying state name
$state.go('dashboard.welcome');
Using $location.path change the route by specifying url
$location.path('/dashboard/welcome');
It sounds like you want links to /welcome to be for state dashboard.welcome. Here is a plunker showing how this can be done. I show two sets of dashboard and welcome states. The first set of states (dashboard & welcome) shows that /dashboard/welcome will bring you to the dashboard.welcome state.
The second set (dashboard2 & welcome2) shows that /welcome will go to state dashboard2.welcome2. I believe this is what you were looking for.
If you hover over the links you can see where they will take you.
https://plnkr.co/edit/AVKPFa?p=info
Nested routes in ui-router get nested urls. I would however recommend using named-views for this kind of structure. You can find more info about it here:
https://github.com/angular-ui/ui-router/wiki/Multiple-Named-Views
The gist of it is: you can specify a named component (ui-view) for your left menu navigation and another one for content, which gives you much more control down the line, because named components can be overwritten in child states or they can keep the default template, depending on your needs.
Example:
.state('root', {
url: '',
abstract: true,
views: {
'header': {
templateUrl: 'templates/partials/header.html',
controller: 'headerCtrl'
},
'logo': {
templateUrl: 'templates/partials/logoView.html'
},
'footer':{
templateUrl: 'templates/partials/footer.html',
controller: 'footerCtrl'
}
}
})
.state('root.login', {
url: '/login',
views: {
'header#': {
template: ''
},
'container#': {
templateUrl: 'templates/login.html',
controller: 'loginController'
}
}
})
.state('root.report', {
url: '/report',
views: {
'container#': {
templateUrl: 'templates/eu_dashboard.html',
controller: 'reportController'
}
}
})
And in your index.html:
<div ui-view="logo"></div>
<div ui-view="header"></div>
<div id="mainView" ui-view="container"></div>
<div ui-view="footer"></div>

Build ui.sref for a child state of a child state

What I am trying to achieve:
In the template of the main state (/model), I have links to a child state phase (/phase), but I also want to be able to link from the main state directly to a given child state of a given phase (/phase/1/task1).
My config:
$stateProvider
.state('model', {
url: '/model',
views: {
'': {
templateUrl: 'model.tpl.html',
controller: 'modelController'
}
},
})
.state('model.phase', {
url: '/phase/{phaseId:int}',
views: {
'#main.model': {
templateUrl: 'model.phase.tpl.html',
controller: 'phaseController'
}
}
})
.state('model.phase.task', {
url: '/task/{taskId:int}',
controller: 'taskController'
})
};
In model.tpl.html I list out all the phases with general information, and for each phase, I list out tasks from that phase like so:
<div ng-repeat="task in tasks | filter:{primary:true, knowledgeAreaId:area.id, phaseId:phase.id}:true" class="task" ng-class="{ active: task.active }">
<div class="task__content" ui.sref="LINKHERE"></div>
</div>
And it is here I want to build a link that goes directly from the main model page to a given child page for a given phase.
What I have tried in model.tpl.html is to add ui.sref like so
<a ui.sref=".phase({ phaseId: task.phaseId}.task({ taskId: task.taskId})">
But that gives me an error, so it seems that you cant 'chain' states with properties.
If I only wanted to link to a phase, then the following works just fine:
<a ui.sref=".phase({ phaseId: task.phaseId}">
Is there any 'correct' soloution in ui.router for this scenario, or do I have to manually build a string for the href value. That way I change the url to the route I want instead of changing to a given state. It seems wrong to do that, when everything else works by switching states.
Something like:
http://localhost/#/model/phase/1/task1
This should work:
ui.sref=".phase.task({ phaseId: task.phaseId, taskId: task.taskId })"

How to use AngularJS's ui.router to implement a create/edit tabbed form?

I created a tabbed edit form like this:
<ul class="nav nav-tabs">
<li role="presentation" ui-sref-active="active">
<a ui-sref="products.edit.general">General</a>
</li>
... more tabs ...
</ul>
My routes look like:
$stateProvider
.state('products.edit', {
abstract: true,
url: '/{product_id:int}/edit',
templateUrl: 'partials/products.edit.html',
controller: 'ProductsEditController'
})
.state('products.edit.general', {
url: '',
templateUrl: 'partials/products.edit.general.html'
})
I have several files like partials/products.edit.general.html for each tab. each of them contains different form fields.
Now, I'm looking to extend this code to create products using the same form fields and just switching between POST/PUT on the controller. To do so, I created the products.new set of routes, but I'm failing to implement them on the HTML above. Or should I set up my .new rules differently?
You can pass data or promises to the controller from the .state configuration as follows
.state('products.edit', {
abstract: true,
url: '/{product_id:int}/edit',
templateUrl: 'partials/products.edit.html',
controller: 'ProductsEditController',
resolve: {
action: function () {
return 'edit';
}
}
});
This will resolve as a dependency (or dependencies if multiple properties used in resolve) of the controller:
angular.module('myApp').controller(function( action, /*other dependencies...*/){
if(action === 'edit'){
// editing specific code or variables
}else{
// create specific code
}
});
See UI Router docs for more details

AngularJS UI Router: ui-sref not updating URL in address bar because parent state has StateParams?

I am using Angular UI Router and seem to be experiencing an odd issue. When I click a link that has a ui-sref directive attached to it, it successfully loads the new view as I would expect, HOWEVER, it does not update the URL bar. I believe this is ocurring because the parent state's url is a dynamic StateParam /:room. How do I get around this issue?
Here is a snippet of my UI Router
// Room
.state({
name: 'room',
url: "/:room",
views: {
"main": {
templateUrl: "views/pages/chat.html",
controller: "RoomCtrl"
},
"login#room": {
templateUrl: "views/partials/_login.html"
},
"navigation#room": {
templateUrl: "views/partials/_navigation.html",
controller: "NavigationCtrl"
}
},
resolve: {
userLocation: function(geolocationFactory) {
return geolocationFactory;
}
}
})
// Share
.state({
name: 'room.share',
url: "/share",
views: {
"share#room": {
templateUrl: "views/partials/_share.html",
controller: "ShareCtrl"
}
}
});
ui-sref
<button id="share-button" ui-sref="room.share">Share</button>
I created a plunker to demonstrate what is happening
So we can navigate among rooms like this:
<a ui-sref="room({room:1})">room 1</a>
<a ui-sref="room({room:2})">room 2</a>
<a ui-sref="room({room:3})">room 3</a>
this will in fact creat the url like this
#/1 // where 1 represents the id of the :room
#/2
#/3
Now, we can navigate to the substate .share without specifying the :room id
<a ui-sref="room.share">room.share</a>
And what will happen? Firstly the place for :room will be empty ... no room is selected.
Secondly - the previously selected room (its :room id) won't be changed. So the resulting URL will depend on the already selected room. If we were in a room 2, the generated url will be:
#//share
but we will be redirected to
#/2/share
becuase there is still $stateParams.room === 2
Finally, we should always pass the complete state signature:
<a ui-sref="room.share({room:1})">room.share({room:1})</a>
<a ui-sref="room.share({room:2})">room.share({room:2})</a>
<a ui-sref="room.share({room:3})">room.share({room:3})</a>
Check that all here (click the top right corner blue icon to open the prview in sepearate window with url)
I had this problem because my parameter names did not match my URL definition.
For example, I had a parameter set like this:
{
page: {...},
results: {...},
sort: {...},
}
...and a URL like this:
url: '/foo?page&results&orderby'
After fixing the mismatch between sort and orderby, the address bar updated as expected.

How to disable static url in angular ui-router?

I have some two ui-router states in my angular application, which works fine, like this:
app.config(["$stateProvider",
function($stateProvider){
$stateProvider
.state("settings", {
url: "/settings",
views: {
"secondMenu": {
templateUrl: "second_menu.html?parent=settings",
controller: "settingsController"
},
"thirdMenu": {
template: empty_template
}
}
})
.state("cabinet", {
url: "/cabinet",
views: {
"secondMenu": {
templateUrl: "second_menu.html?parent=cabinet",
controller: "cabinetController"
},
"thirdMenu": {
template: empty_template
}
}
})
But only part of my menu i need work with angular. Other part of menu must go to static (not angular) url of my site, like this:
<ul>
<li>static-url</li>
<li><a ui-sref="cabinet">cabinet angular url</a></li>
<li><a ui-sref="settings">settings angular</a></li>
</ul>
And when i click on angular-urls - i get need information in my ng-view , but if after then i click on static-url - my url changing from /#/cabinet (for ex.) to /static-url/, but query to server does not exist. Page content saving as in cabinet angular url.
How I can disabling angular ui-router working on /static-url/ ?
So for future lurkers, here's what you need to do. Just add this attribute to your static page's anchor tag:
target="_self"
Like this:
<ul>
<li>static-url</li>
<li><a ui-sref="cabinet">cabinet angular url</a></li>
<li><a ui-sref="settings">settings angular</a></li>
</ul>

Resources