I have the following routing configuration:
myApp.config(function ($stateProvider) {
$stateProvider
.state('courses',
{
url: '/courses',
templateUrl: '/js/courses/courses.html',
controller: 'CoursesCtrl'
})
.state('courses.course',
{
url: '/{courseId}',
templateUrl: '/js/courses/course.html',
controller: 'CourseCtrl'
})
.state('courses.course.lessons',
{
url: '/lessons',
templateUrl: '/js/courses/lessons.html',
controller: 'LessonsCtrl',
})
.state('courses.course.about,
{
url: '/about,
templateUrl: '/js/courses/about.html'
})
;
});
I have a reference in the page like this
<a ui-sref="courses.course({courseId: c.id})"> Course # {{c.id}}</a>
This reference leads to the view of the course’s details.
This view has a nested view, which allows seeing different detail of the course such as lessons list and description. I want to have a direct reference to the view, which contains lessons list of the particular course.
I’ve tried something like ui-sref="courses.course({courseId: c.id}).lessons", but it leads to the error.
Could you tell me what is the right way?
this is the syntax
<a ui-sref="courses.course.lessons({courseId: c.id})"> Course # {{c.id}} lessons </a>
and check you have defined parent state or not..
Related
My oversimplified app.config() has:
$stateProvider.
state("/", {
url: "/",
templateUrl: "main.html"
}).
state("/newCategories", {
url: "/categories/new",
templateUrl: "/views/new_categories.html",
controller: "newCategoriesCtrl"
}).
state("/categoryPages", {
url: "/categories/:address",
templateUrl: "/views/categories.html",
controller: "categoriesCtrl",
resolve: {
categoriesDataResolve: function resolveTemplate($stateParams, DataResolver) {
return DataResolver.resolveTemplates($stateParams.address);
}
}
});
With this I can use ui-serf link with "/newCategories" to load its url: "/categories/new"
<a ui-sref="/newCategories">New Category</a>
However, when I refresh, it thinks that "/new" is part of $stateParams. Therefore it uses a different controller and tries to resolve its template (which is missing, so it gives an error).
For now I fixed it by changing the url from "/categories/new" to "/categories-new" so it won't get confused on refresh. But how do I solve this issue differently? (Maybe ui-router has a some way of dealing with it)
If I understand you right, you want to call different controller a.e. newCategoriesCtrl when user calls /categories/:address where address param is new
Changing /categories/new to "/categories-new is a right way to solve it.
Small tip: its not good practice to use / as prefix for state name. It confuses the developer and can be mixed with original URL.
$stateProvider.
//...
state("newCategories", {
url: "/categories-new",
templateUrl: "/views/new_categories.html",
controller: "newCategoriesCtrl"
}).
state("categoryPages", {
url: "/categories/:address",
templateUrl: "/views/categories.html",
controller: "categoriesCtrl",
resolve: {
//...
}
});
I'm new to Ionic and AngularJS so I'm struggling with (hopefully) something simple. I have these two states:
.state('menu', {
url: '/mobile/beer',
controller: 'beerCtrl',
templateUrl: 'templates/beer-menu.html'
})
.state('detail', {
url: '/mobile/beer/:id',
controller: 'beerDetailCtrl',
templateUrl: 'templates/beer.html',
resolve: {
beer: function($stateParams, beerService) {
return beerService.findBeerById($stateParams.id)
}
}
})
The 'menu' state fetches all beers from a server and hand the list over to 'beer-menu.html' that contains an ion-list to display the list. When the user selects a beer in the list, the 'detail' states steps in to display more details about the beer.
It's all working but I want to improve it a bit by using an abstract state:
.state('beer', {
abstract: true,
url: '/mobile/beer'
})
.state('beer.menu', {
url: '',
controller: 'beerCtrl',
templateUrl: 'templates/beer-menu.html'
})
.state('beer.detail', {
url: '/:id',
controller: 'beerDetailCtrl',
templateUrl: 'templates/beer.html',
resolve: {
beer: function($stateParams, beerService) {
return beerService.findBeerById($stateParams.id)
}
}
})
It looks totally OK to me - esp because this is based on one of the 'Formulas' from the Ionic Framework website. Nevertheless the app stops working when I structure the states like that. I've tried to tweak it in various way but couldn't get it working. Do you see what's wrong?
As the UI-Router documentation you have to define also a template for abstract state 'beer':
Remember: Abstract states still need their own <ui-view/> for their children to plug into. So if you are using an abstract state just to prepend a url, set resolves/data, or run an onEnter/Exit function, then you'll additionally need to set template: "<ui-view/>".
See:
https://github.com/angular-ui/ui-router/wiki/Nested-States-and-Nested-Views#abstract-states
I have a request to add in another URL parameter that directs to a state that I already have set up. For efficiency purposes, I'm trying to see if I can add multiple URLs to point to the same state, or should I just use the $UrlRouterProvider.when() method to re-direct to that state in this new case.
Ex. this is what already exists
.state('site.link1',
{
url: '/link1',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
and the request is to add www.site.com/newlink that points to the link1 page. Is there something like this;
.state('site.link1',
{
url: '/link1, /newlink',
...
Try using the Regex and a parameter in the url. It is not optimal but works.
.state('site.link1',
{
url: '/{path:link1|newlink}',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
More information on regex in Urls.
To generate links with ui-sref pass the same parameter with the state name as a function
<a ui-sref="site.link1({path:'link1'})" >site link 1</a>
<a ui-sref="site.link1({path:'newlink'})">site new link</a>
You use params:
https://github.com/angular-ui/ui-router/wiki/URL-Routing
.state('site.link',
{
url: '/{link}'
..
}
so when you use the same state like this
$state.go('site.link', {link: 'link1'})
$state.go('site.link', {link: 'link2'})
you can used when() function
.state('site.link1',
{
url: '/link1',
templateUrl: '/views/link1.html',
controller: 'link1Ctrl'
})
then on root config
angular.module('myApp', [...])
.config(function ($urlRouterProvider) {
$urlRouterProvider.when(/newlink/, ['$state','$match', function ($state, $match) {
$state.go('site.link1');
}]);
});
I found this approach to be quite simple and clean: create two equal states, just changing the url property
//Both root and login are the same, but with different url's.
var rootConfig = {
url: '/',
templateUrl:'html/authentication/login.html',
controller: 'authCtrl',
data: {
requireLogin: false
}
}
var loginConfig = Object.create(rootConfig)
loginConfig.url = '/login'
$stateProvider
.state('root', rootConfig)
.state('login', loginConfig)
I had almost the same problem, only with another constraint - I didn't want to use a redirect, since I wanted the url in the browser to stay the same, but display the same state.
This was because I wanted the chrome saved passwords to work for users that already saved the previous url.
In my case I wanted these two urls :
/gilly and
/new/gilly
to both point to the same state.
I solved this by having one state defined for /gilly, and for the second url, I defined an abstract state called /new.
This should be set up like this :
$stateProvider.state('new', {
abstract: true,
url: '/new'
template: '',
controller: function() { }
}).state('gilly', {
url: '/gilly',
template: 'gilly.html',
controller: 'GillyController'
}).state('new.gilly', {
url: '/gilly', // don't add the '/new' prefix here!
template: 'gilly.html',
controller: 'GillyController'
});
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.
While using ui-view in AngularJS, I'm trying to use URL parameters inside of nested views.
For entities of a given type, I want to be able to show all of the entities when navigating to the url /entities, but I would also like to see only the entities of a given type if I go to entities/ofcategory/categoryName. Lastly, I also want to be able to navigate to /entities/details/entityName to see the details of one entity.
Is this structure possible?
This is my ui-routercode:
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('entities', {
url: '/entities',
templateUrl: 'app/entities/views/entities.html'
})
.state('entities.ofcategory', {
url: '/ofcategory/:categoryName',
templateUrl: 'app/entities/views/entities.ofcategory.html'
}
.state('entities.details', {
url: '/details/:entityName',
templateUrl: 'app/entities/views/entities.details.html'
});
}]);
If I'm navigating to entities/ofcategory/aname or /entities/details/aname I enter the regular entities controller instead of the category or detailsController
One option is to add an abstract state, which serves as a parent to all your entities states. In that case all your urls become relative to this parent state. Please note that you have to define a ui-view in the template of the abstract state, so it could be used for loading the child templates.
app.config(['$stateProvider', function ($stateProvider) {
$stateProvider
.state('entities', {
url: '/entities',
abstract: true,
templateUrl: 'app/entities/views/entities.html'
})
.state('entities.all', {
url: '/',
templateUrl: 'app/entities/views/entities.all.html'
})
.state('entities.ofcategory', {
url: '/:categoryName',
templateUrl: 'app/entities/views/entities.ofcategory.html'
}
.state('entities.details', {
url: '/details/:entityName',
templateUrl: 'app/entities/views/entities.details.html'
});
}]);
app/entities/views/entities.html:
<div>
<h1>Entities<h1>
<div ui-view></div>
</div>