Nested UI-Route - angularjs

I am playing with nested view from ui-route, but when I try to access a nested template I get an error. My state:
$stateProvider.state('invoice',{
url: '/invoice',
views: {
'#': {
templateUrl: 'views/invoice.v01/paths/invoice.pocketbits.html'
},
'list#invoice': {
templateUrl: 'views/invoice.v01/paths/database.pocketbits.html'
}
}
});
localhost/#/invoice works fine but when I try to click "ui-sref="list" from inside I get:
Error: Could not resolve 'list' from state 'invoice' at
Object.transitionTo (angular-ui-router.js:3141)
Although my list template is inside the invoice.

Actually the string you give to ui-sref should refer to a state name.
I mean actually you try to go to a state list that you didn't declared.
$stateProvider.state('invoice',{
url: '/invoice',
views: {
'#': {
templateUrl: 'views/invoice.v01/paths/invoice.pocketbits.html'
},
'list#invoice': {
templateUrl: 'views/invoice.v01/paths/database.pocketbits.html'
}
}
});
This state is called "invoice" and have two static nestedview # and list#invoice
You actually can't go to list state.
You may want something like that :
$stateProvider.state('invoice',{
url: '/invoice',
templateUrl: 'views/invoice.v01/paths/invoice.pocketbits.html'
});
$stateProvider.state('invoice.list',{
url: '/list',
templateUrl: 'views/invoice.v01/paths/database.pocketbits.html'
});
To be clear. In you index.html you have a ui-view.
When you do to state invoice you will replace this ui-view with the template from the invoice state. Your invoice state template will have a ui-view inside. When you go to state invoice.list you'll add the invoice.list template into this ui-view.
EDIT :
According to your comment this is the usecase you're looking at.
If you want to have two separate view (ie : when you switch from invoice to list you'll replace the content of the index.html ui-view) you should do this like this :
$stateProvider.state('invoice',{
url: '/invoice',
templateUrl: 'views/invoice.v01/paths/invoice.pocketbits.html'
});
$stateProvider.state('list',{
url: '/list',
templateUrl: 'views/invoice.v01/paths/database.pocketbits.html'
});
Hope it helped. If you provide more information on what you try to achieve i could add an exemple of your case and make it working on plunker.

Related

Default child state with same url as parent

I have app with many main states, one of them is user profile:
$stateProvider.state('profile', {
url: '/profile/',
templateUrl: 'profile/profile.html',
controller: 'Profile',
});
But this is just an container for nested pages with different profile settings. It's template only contains main menu and ui-view for nested states. Controller is only for that menu handling.
One of nested views should be default url and have same URL as parent, so there shouldn't be any suffixes added into url, but I can't achieve that.
Here's what I tried:
$stateProvider.state('profile.details', {
url: '',
templateUrl: 'profile/details.html',
controller: 'ProfileDetails',
});
this is not working at all, at url /profile/ only menu appears and an empty ui-view element. Second approach:
$stateProvider.state('profile.details', {
url: '/',
templateUrl: 'profile/details.html',
controller: 'ProfileDetails',
});
This matches on url /profile// (with 2 slashes at end). At url /profile/ there is still menu and empty ui-view element.
How can I achieve that result? Is this even possible using angular-ui-router?
Make your parent state abstract. This will prevent from going into that state, and force to go to child states only. Abstract states are perfect as templates for child ones. Also get rid of url:
$stateProvider.state('profile', {
abstract: true,
templateUrl: 'profile/profile.html',
controller: 'Profile',
});
Now for your child state define absolute URL
$stateProvider.state('profile.details', {
url: '^profile',
templateUrl: 'profile/details.html',
controller: 'ProfileDetails',
});
That should work.

In ui-router, is it possible to have nested views within nested views?

I am doing my routing with ui-router and have a nested view that loads up some menu options for the item that is selected. My question is, is it possible to have nested views within a nested view with ui-router?
The code here works:
.state('settings', {
url: '/settings',
views: {
'': {
templateUrl: './templates/settings.html',
controller: 'settingsCtrl'
},
'details#settings': {
templateUrl: './templates/details.html',
controller: 'detailCtrl'
}
}
})
But say I wanted to load something attached to the details#settings view. How would I go about that? I have tried
.state('settings', {
url: '/settings',
views: {
'': {
templateUrl: './templates/main/settings.html',
controller: 'settingsCtrl'
},
'detail#settings': {
url: '',
views: {
'': {
templateUrl: './templates/detail.html',
controller: 'detailCtrl'
},
'contact#detail#settings': {
templateUrl: './templates/contactpref.html'
}
}
}
}
})
I don't get any errors in the console on this, but it does break my original nested view. From what I have found the docs aren't really clear on if this is even possible.
It is not possible. Check the: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$stateProvider
and section views:
an optional map<string, object> which defined multiple views, or targets views manually/explicitly.
Examples:
Targets three named ui-views in the parent state's template
views: {
header: {
controller: "headerCtrl",
templateUrl: "header.html"
}, body: {
controller: "bodyCtrl",
templateUrl: "body.html"
}, footer: {
controller: "footCtrl",
templateUrl: "footer.html"
}
}
That is it. That's what UI-Router will see and resolve for us. And why are not you provided with any error?
Because expect value of the map<string, object> is object. It is iterated for known properties (controller, templateUrl) - and any uknown is ignored, not used, not found... skipped
Solution:
After some experience with UI-Router I would suggest:
if there are some features related - place them in state
if there are some more features related, but only with some conditions - place them into child state.
That structure will bring many benefits, mostly: stable parent (not reloading always) and dynamic child - placing new and new content into some targets/anchors/ui-views whenever parameter is changed. Check:
Trying to have one subview call another subview using ui-router

Angular-ui.router: Update URL without view refresh

I have an Angular SPA that presents a variety of recommendation lists, and a Google Map of locations, based on different cuts of some restaurant data (see m.amsterdamfoodie.nl). I want each of these lists to have their own URL. In order for Google to crawl the different lists I use <a> tags for the offcanvas navigation.
At present the <a> tag causes a view refresh, which is very noticeable with the map.
I can prevent this using ng-click and $event.preventDefault() (see code snippets below), but then I need to implement a means of updating the browser URL.
But in trying Angular's $state or the browser's history.pushstate, I end up triggering state changes and the view refresh...!
My question is therefore how can I update a model and the URL, but without refreshing the view? (See also Angular/UI-Router - How Can I Update The URL Without Refreshing Everything?)
I have experimented with a lot of approaches and currently have this html
Budget
In the controller:
this.action = ($event) ->
$event.preventDefault()
params = $event.target.href.match(/criteria\/(.*)\/(.*)$/)
# seems to cause a view refresh
# history.pushState({}, "page 2", "criteria/"+params[1]+"/"+params[2]);
# seems to cause a view refresh
# $state.transitionTo 'criteria', {criteria:params[1], q:params[2]}, {inherit:false}
updateModel(...)
And, what is I think is happening is that I am triggering the $stateProvider code:
angular.module 'afmnewApp'
.config ($stateProvider) ->
$stateProvider
.state 'main',
url: '/'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
.state 'criteria',
url: '/criteria/:criteria/:q'
templateUrl: 'app/main/main.html'
controller: 'MainCtrl'
controllerAs: 'main'
One possible clue is that with the code below if I load e.g. http://afmnew.herokuapp.com/criteria/cuisine/italian then the view refreshes as you navigate, whereas if I load http://afmnew.herokuapp.com/ there are no refreshes, but no URL updates instead. I don't understand why that is happening at all.
This is an example of the way to go if I understand correctly:
$state.go('my.state', {id:data.id}, {notify:false, reload:false});
//And to remove the id from the url:
$state.go('my.state', {id:undefined}, {notify:false, reload:false});
From user l-liava-l in the issue https://github.com/angular-ui/ui-router/issues/64
You can check the $state API here: http://angular-ui.github.io/ui-router/site/#/api/ui.router.state.$state
Based on our previous discussions, I want to give you some idea, how to use UI-Router here. I believe, I understand your challenge properly... There is a working example. If this not fully suites, please take it as some inspiration
DISCLAIMER: With a plunker, I was not able to achieve this: http://m.amsterdamfoodie.nl/, but the principle should be in that example similar
So, there is a state definition (we have only two states)
$stateProvider
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
},
'right#main' : { templateUrl: 'tpl.right.html',},
'map#main' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list#main' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'map' : {
templateUrl: 'tpl.map.html',
controller: 'MapCtrl',
},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
}];
This would be our main tpl.layout.html
<div>
<section class="main">
<section class="map">
<div ui-view="map"></div>
</section>
<section class="list">
<div ui-view="list"></div>
</section>
</section>
<section class="right">
<div ui-view="right"></div>
</section>
</div>
As we can see, the main state does target these nested views of the main state: 'viewName#main', e.g. 'right#main'
Also the subview, main.criteria does inject into layout views.
Its url starts with a sign ^ (url : '^/criteria/:criteria/:value'), which allows to have / slash for main and not doubled slash for child
And also there are controllers, they are here a bit naive, but they should show, that on the background could be real data load (based on criteria).
The most important stuff here is, that the PARENT MainCtrl creates the $scope.Model = {}. This property will be (thanks to inheritance) shared among parent and children. That's why this all will work:
app.controller('MainCtrl', function($scope)
{
$scope.Model = {};
$scope.Model.data = ['Rest1', 'Rest2', 'Rest3', 'Rest4', 'Rest5'];
$scope.Model.randOrd = function (){ return (Math.round(Math.random())-0.5); };
})
.controller('ListCtrl', function($scope, $stateParams)
{
$scope.Model.list = []
$scope.Model.data
.sort( $scope.Model.randOrd )
.forEach(function(i) {$scope.Model.list.push(i + " - " + $stateParams.value || "root")})
$scope.Model.selected = $scope.Model.list[0];
$scope.Model.select = function(index){
$scope.Model.selected = $scope.Model.list[index];
}
})
This should get some idea how we can use the features provided for us by UI-Router:
Absolute Routes (^)
Scope Inheritance by View Hierarchy Only
View Names - Relative vs. Absolute Names
Check the above extract here, in the working example
Extend: new plunker here
If we do not want to have map view to be recreated, we can just omit that form the child state def:
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
// 'map' : {
// templateUrl: 'tpl.map.html',
// controller: 'MapCtrl',
//},
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
},
},
})
Now our map VIEW will be just recieving changes in the model (could be watched) but view and controller won't be rerendered
ALSO, there is another plunker http://plnkr.co/edit/y0GzHv?p=preview which uses the controllerAs
.state('main', {
url: '/',
views: {
'#' : {
templateUrl: 'tpl.layout.html',
controller: 'MainCtrl',
controllerAs: 'main', // here
},
...
},
})
.state('main.criteria', {
url: '^/criteria/:criteria/:value',
views: {
'list' : {
templateUrl: 'tpl.list.html',
controller: 'ListCtrl',
controllerAs: 'list', // here
},
},
})
and that could be used like this:
<h4>{{main.hello()}}</h4>
<h4>{{list.hello()}}</h4>
The last plunker is here
you can use scope inheritance to update url without refreshing view
$stateProvider
.state('itemList', {
url: '/itemlist',
templateUrl: 'Scripts/app/item/ItemListTemplate.html',
controller: 'ItemListController as itemList'
//abstract: true //abstract maybe?
}).state('itemList.itemDetail', {
url: '/:itemName/:itemID',
templateUrl: 'Scripts/app/item/ItemDetailTemplate.html',
controller: 'ItemDetailController as itemDetail',
resolve: {
'CurrentItemID': ['$stateParams',function ($stateParams) {
return $stateParams['itemID'];
}]
}
})
if child view is inside parent view both controllers share same scope.
so you can place a dummy (or neccessary) ui-view inside parent view which will be populated by child view.
and insert a
$scope.loadChildData = function(itemID){..blabla..};
function in parent controller which will be called by child controller on controller load. so when a user clicks
<a ui-sref="childState({itemID: 12})">bla</a>
only child controller and child view will be refreshed. then you can call parent scope function with necessary parameters.
The short answer ended up being do not put the map inside a view that changes. The accepted answer provides a lot more detail on how to structure a page with sub-views, but the key point is not to make the map part of the view but to connect its behaviour to a view that does change and to use a Controller to update the market icons.

ionic routing issue, shows blank page

I started building ionic app on top of the sidemenu starter app. The starter app has a base state 'app' which is abstract and all the sidemenu pages are children of the app for example app.search, app.browse, app.playlists etc.
I have similar hierarchy. However, I want the start page to be some other page, which means it is at the app level.
The states look like this:
$stateProvider
.state('app', {
url: "/app",
abstract: true,
templateUrl: "templates/menu.html",
controller: 'AppCtrl'
})
.state('join', {
url: "/join",
views: {
'menuContent' :{
templateUrl: "templates/join.html",
controller: 'joinCtrl'
}
}
})
.state('app.search', {
url: "/search",
views: {
'menuContent' :{
templateUrl: "templates/search.html",
controller: 'searchCtrl'
}
}
})
.state('app.results', {
url: "/results",
views: {
'menuContent' :{
templateUrl: "templates/results.html",
controller: 'resultsCtrl'
}
}
});
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/join');
When I run the app, the url defaults to
http://192.168.1.4:8100/#/join
and shows a blank page. Obviously, the join.html is not blank. Also, the console.log messages in joinCtrl are not outputted.
I am not able to figure out why is it not loading the join page. When I change the otherwise to point to '/app/search', everything works.
Any idea what's going on? How do I load the initial page by default and then navigate to the 'app.search' state?
I would expect that because the app is abstract - it is there for a reason. To be parent/layout state. In its template should most likely live all other states.
If yes - check this working example I created to demonstrate that. What we need is to mark the join as a child of the app state. Then the 'menuContent' placeholder will be properly searched in the app template:
.state('join', {
parent: 'app',
url: "^/join",
views: {
'menuContent': {
templateUrl: "tpl.join.html",
controller: 'joinCtrl'
}
}
})
There is a working plunker
The definition url: "^/join", is there to support the idea, that the url defined like this:
// if none of the above states are matched, use this as the fallback
$urlRouterProvider.otherwise('/join');
will work even for nested state (join is child of app). See:
Absolute Routes (^)
If you want to have absolute url matching, then you need to prefix your url string with a special symbol '^'.
This is just one way... we can do the similar stuff if the join is not nested state, but then it should target the unnmaed view '' instead of 'menuContent'

ui-router: A route with no view template

Is it possible to setup a route in ui-router that only has a controller? The purpose being that at a certain URL, the only thing I'd like to do is take action programatically, and not display anything in terms of a view. I've read through the docs, but I'm not sure if they offer a way to do this.
Yes, I have read this: https://github.com/angular-ui/ui-router/wiki/Frequently-Asked-Questions#how-to-open-a-dialogmodal-at-a-certain-state, but that is not quite what I am looking for.
For example, let's just say I have a basic body with view:
<body ui-view></body>
And some basic config:
// Routes
$stateProvider
.state('myaction', {
url: "/go/myaction",
onEnter: function() {
console.log('doing something');
}
});
When /go/myaction is visited, the view is blank. Is it possible to do this?
I was able to solve this problem by redirecting the headless state I was taking programmatic action in, to a state WITH a view at the end of the headless state:
$stateProvider
.state('myaction', {
url: "/go/myaction",
onEnter: function() {
console.log('doing something');
}
controller: function($state) {
$state.go('home');
}
});
You can't have a controller without a view but you can use onEnter instead of a controller. If you don't want to change the current view when accessing this state you can define it as a child state:
$stateProvider
// the parent state with a template
.state('home', {
url: '/home',
templateUrl: '/home.html',
controller: 'HomeCtrl'
})
// child of the 'home' state with no view
.state('home.action', {
url: '/action',
onEnter: function() {
alert('Hi');
},
});
Now in home.html you can do something like this:
<a href ui-sref=".action">Greet me!</a>
From the docs:
Warning: The controller will not be instantiated if template is not defined.
Why don't you use an empty string as a template to overcome this?
Yes, you can do that. Use absolute view names to re-use the <ui-view> of another state.
Take a look at this example:
Users go to my app, but depending on them being authenticated or not, I want to send them to a public or private page. I use the index state purely to see if they're logged in or not, and then redirect them to index.private or index.public.
The child states make use of absolute view names to use the <ui-view> element that corresponds to the index state. This way, I don't need to make a second nested <ui-view>.
$stateProvider.state('index', {
url: "/",
controller: 'IndexCtrl'
}).state('index.private', {
views: {
"#": {
templateUrl: 'private.html',
controller: 'PrivateCtrl'
}
}
}).state('index.public', {
views: {
"#": {
templateUrl: 'public.html',
controller: 'PublicCtrl'
}
}
});
A small note on this example: I'm using the # shortcut here. Normally you would use viewname#statename.
My solution for this was just to include a template (html file) that is blank.

Resources