I have a ui-router state that I use to display some info about a movie in an overlay. The data it displays is always the same, and the data it recieves is also always the same. But I'm using that state multiple times in my ui-router controller, but with different state parameters.
I have 4 states from where I call this trailer state,
home.container-big.trailer
home.suggestions.container-big.trailer
friendprofile.container-big.trailer
home.activities.movie-info.trailer
These are the 4 states I call the trailer state.
So for each click I have a different state although everything except the state name is different.
So I was thinking this,
I've create a stateProvider
$stateProvider
.state('trailer',{
params: {
trailer_link: null
},
url: '',
views: {
"youtube_trailer":{
templateUrl: '../assets/angular-app/templates/_container-trailer.html',
controller: function($scope, $stateParams, $state) {
$scope.trailer_link = $stateParams.trailer_link;
}
}
}
})
And I want to call it inside the home.container-big state like so,
%a{"ui-sref" => ".trailer({trailer_link: '{{ trailer.link }}'})"} Trailer {{$index+1}}
The click produces this error
Could not resolve '.trailer' from state 'home.container-big'
Which makes sence. So I'm thinking I need to pass the current state in the click to the name of the ui-state. Is this possible? Or is there another way I can reuse the same state in different states?
Related
Similar to this question and this issue I'm trying to use URL query parameters to define child states with ui-router :
.state('map', {
url: '/map?country&city'
templateUrl: 'templates/myMapTemplate.html',
// no resolve in this case as using default data for map template
})
.state('map.country', {
url: '?country',
templateUrl: 'templates/myMapTemplate.html',
resolve: {
countryData : ['mapServices', '$stateParams', function(mapServices, $stateParams){
return mapServices.getCountryBounds($stateParams.country) // ajax server call
}]
}
})
.state('map.city', {
url: '?city',
templateUrl: 'templates/myMapTemplate.html',
resolve: {
cityData : ['mapServices', '$stateParams', function(mapServices, $stateParams){
return mapServices.getCityBounds($stateParams.city) // ajax server call
}]
}
})
The idea is that:
- having a different query parameter (or none at all) changes the state
- the resolve: of each child state can be specified to get different information from the server
- the template is the same for each state, but the data (ultimately) fed to the controller / template is different for each child state (retrieved via resolve)
eg.
www.mysite.com/map loads map with default data
www.mysite.com/map?country=US gets country data from server
www.mysite.com/map?city=sao_paulo gets city data from server
I've tried to set this up as above, but it won't work:
- no child state is recognised when adding URL parameters in the child state
Is it possible to use (optional) query parameters to change child state in ui-router?
(The docs don't specify much regarding query params)
Update #1: Created a plunker of the code above
Update #2: Created a 2nd plunker defining /map as an abstract state - which now works for changing states with ui-sref (and injects parameters into the resolve) - but href map?country=US still not recognised... ?
I have a simple form that can be used to initiate a forecast request. I created this as a parent state requests (Initiate Forecast).
Desired behavior
When a request is submitted, that immediate request is shown in a child state (View Status) as most recent request. This View Status state will also hold a grid of all past requests, meaning I will be refreshing the grid with data every time this state is invoked.
Both parent and child states are navigable from a sidebar menu.
So, if a user clicks on parent (Initiate Forecast), he should be able to see only the form to submit a request. If a user directly clicks on the 'View Status'(child), then he should be able to see both the form and the grid of requests.
app.js
function statesCallback($stateProvider) {
$stateProvider
.state('requests', {
url: '',
views: {
'header': {
templateUrl: 'initiateforecasting.html',
controller: 'requestsInitiateController'
},
'content': {
template: '<div ui-view></div>'
}
},
params: {
fcId: null,
fcIndex: null
}
})
.state('requests.viewStatus', {
url: '/ViewStatus',
templateUrl: 'viewstatus.html',
controller: 'requestsStatusController'
});
}
var requestsApp = angular.module('requestsApp', ['ui.router']);
requestsApp.config(['$stateProvider', statesCallback]);
requestsApp.run(['$state', function($state) {
$state.go('requests');
}]);
Plunker of my attempts so far.
This is all working for me as shown in the plunker, but I am doing it by not setting a URL to the parent. Having no URL for the parent state is allowing me to see both states. If I add a URL to parent state, then clicking on View Status link is not going anywhere (it stays on Initiate).
How do I change this so that I can have a URL for parent state and still retain the behaviour I need from the parent and child states as described above?
Note: I am fine without the URL for parent state in standalone sample code, but when I integrate this piece with backend code, having no URL fragment on the parent state is making an unnecessary request to the server. This is visible when I navigate to the child state and then go to the parent state. It effectively gives the impression of reloading the page which I think is unnecessary and can be avoided if a URL can be set to the parent state.
You shall not directly write url when using ui.router, try like this:
<a ui-sref="requests.viewStatus">View Status</a>
You are writing state name in ui-sref directive and it automatically resolves url. It's very comfortable because you can change urls any time and it will not break navigation.
I'm currently working on a small application that shows a 'drill down' type of menu. Unlike a TreeView my component only displays the current level.
There are only 3 levels: main, submenu, item. Drilling deeper (on the item) will result in a new state that corresponds to a detail view of the selected item.
I have only 2 states, the first is valid for any of the three levels. The second is the detail state:
$stateProvider.state('menu', {
url: '/menu/:submenu/:item',
templateUrl: 'src/menu/views/menu.html',
controller: 'MenuController',
controllerAs: 'MenuController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
}).state('menu.details', {
url: '/:selectedItem',
templateUrl: 'src/menu/views/menu-details.html',
controller: 'MenuDetailsController',
controllerAs: 'MenuDetailsController',
resolve: {
data: function($stateParams, MenuService){
return MenuService.loadUri($stateParams);
}
}
});
This solutions works correctly, however I'm a bit annoyed by the fact I need to define the resolvemethod twice. I am required to do this as $stateParams contains only the params of that exact state (excluding child states) and I need all parameters to form a valid URL to fetch the corresponding resource via the MenuService.
I know that state.params can be used to get all state params, however it doesn't seem to work in the resolve method: it contains the parameters of the previous state.
Does anybody know how I could fix this? Having a child state that should simply trigger a new view, whilst using the resolve method of the parent state with all state parameters?
If you think I'm completely misunderstanding how states work, then please go ahead and tell me :)
I have an angular app with a homepage that shows a list of things. Each thing has a type. In the nav, there are selectors corresponding to each thing type. Clicking one of these selectors causes the home controller to filter the things shown to those of the selected type. As such, I see the selectors as corresponding to states of the home page.
Now, I'd like to map each of these states to a url route: myapp.com/home loads the home page in default (unfilitered) state, myapp.com/home/foo opens the home page with the foo-type selector activated, and switching from there to myapp.com/home/bar switches to the bar-filtered state without reloading the page.
It's that last bit - triggering "state" changes without reloading the page, that's been particularly tricky to figure out. There are numerous SO/forum questions on this topic but none have quite hit the spot, so I'm wondering if I'm thinking about this in the wrong way: Should I be thinking of these "states" as states at all? Is there a simpler approach?
Also, I'm open to using either ngRoute or ui.router - is there anything about one or the other that might make it simpler to implement this?
Using ui-router, you can approach it like this:
$stateProvider
.state('home', {
url: "/home",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
.state('home.filtered', {
url: "/{filter}",
controller: "HomeController",
templateUrl: "home.html"
// .. other options if required
})
This creates a filtered state as a child of the home state and means that you can think of the URL to the filtered state as /home/{filter}. Where filter is a state parameter that can then be accessed using $stateParams.
Since you don't want to switch views, you inject $stateParams into your controller, watch $stateParams.filter, and react to it how you wish.
$scope.$watch(function () { return $stateParams.filter }, function (newVal, oldVal) {
// handle it
});
I have 3 nested states:
$stateProvider
.state('A', {
url: '/a',
templateProvider: function ($templateCache) {
return $templateCache.get('pages/a.html');
},
controller: 'aController'
})
.state('A.B', {
url: '/b',
templateProvider: function ($templateCache) {
return $templateCache.get('pages/b.html');
}
controller: 'bController'
})
.state('A.B.C', {
url: '/c',
templateProvider: function ($templateCache) {
return $templateCache.get('pages/c.html');
},
controller: 'cController'
})
Let's say that A is the initial state of the app. Now, when a link to state B is clicked in state A, $state.go('.B', ...) is called, state B has to get parameters from A so it can call some service with that parameters and render the data.
Likewise, when a link to state C is clicked on state B, C has to get parameters from B so it can do it's work and display data properly.
First question:
What is the best way to pass those parameters down the line? Is it wrong to nest controllers so parent scope is visible in child scope? Or should they be passed as params?
Second question (kind of dependent on the answer to the first one):
How should the html templates be structured (especially with regards to the 'ui-view' directive), so when the browser back button is clicked on state C after coming from B, the controller for template B doesnt get triggered, so that state B displays the same data as before going to state C without reloading. The same goes for clicking back on state B after coming to it from A.
Third question:
If the user has made the following transitions: A->B->C, and then navigates to some unrelated state D (by clicking the link on the main menu for example), and then in state D presses the back button, how do I prevent controller C from falling apart since it has no input params?
Fourth question (related to third):
Premise: The only 'right' way to get to state B is by clicking on state A, likewise to get to C the user has to pass A->B first.
How, then, do I handle when the user manually enters for example URL B, from some unrelated state D? Again, everything falls apart because B has no input params.
Thank you.
The 1st, 3rd, and 4th questions can be solved by using parameters on the url of your states. For instance, the URL of state A.B can be /b?x&y&z instead of just /b. Then to transition from A to A.B, you would use:
$state.go('A.B', {x: "val1", y: "val2", z: "val3"});
This solves the first problem because you're passing parameters from A to B. This solves the third problem because state A.B.C would not fall apart after returning from another state, since the URL history contains the parameters. And it's very apparent how this approach will solve the fourth problem of user entering the URL directly into the address bar.
Question 2 presents a problem slightly trickier to solve. But what you can do is introduce an intermediate state between A and B whose sole purpose is to prevent the re-loading of state A's controller when the back button is pressed.
.state('A', {
abstract: true,
url: '/a',
templateProvider: function ($templateCache) {
return $templateCache.get('pages/a.html');
},
controller: 'aController'
})
.state('A.int', {
url: '/a/view',
template: <div></div>,
controller: 'aIntermediateController'
})
.state('A.int.B', {
url: '/b',
templateProvider: function ($templateCache) {
return $templateCache.get('pages/b.html');
}
controller: 'bController'
})
Notice, I made state A abstract so that you can't navigate to it directly. Instead, you navigate to A.int directly. When you initially load state A.int, aController will execute once, before aIntermediateController executes. Subsequently, any state changes to A.int will only execute aIntermediateController. And that's how you can prevent aController from running multiple times.
Similarly, you can add an intermediate view between state B and C the same way.