Resolve on parent state doesn't run on each child state change - angularjs

I am building an AngularJS app (using router-ui) that uses a tab style navigation. The users page, for example, will have the following tabs:
Listing (will always be the main tab and list all users. Upon selecting a user on this tab, the rest will be come active
Account Details
Contact Details
Notes
etc
Now, I am using a service called entityIdPersistenceService to remember the ID of the user selected on the listing tab. In my routes, I am doing something like the below to notify the child tabs of the selected userId (so they can grab the necessary data), however it seems like alot of repetitive code. Is there a better way to provide my selected id to all other tabs?
resolve: {
entityId: function(entityIdPersistenceService) {
return entityIdPersistenceService.getId();
}
}
It seems like alot of repetitive code. I added it to the parent state, however it looks like the code only runs once on the parent state, and not on every child state change.
Are there any better ways of doing something? Ideally id like to put this on the parent state and have it run at each route change.n

All pages related to a user should have UserId somewhere in the url and in route parameters:
.state('user.accountDetails', {
url: "/users/{userId}/account",
templateUrl: "..."
})
.state('user.contactDetails', {
url: "/users/{userId}/contact",
templateUrl: "..."
})
In this way the UserId will be stored in url instead of the service and every child controller will be able to get it via $stateParams.userId

Related

Angular UI-Router: How to call child state without its parent?

We have recently move our application to Ui-Router from ng-route. And we are trying to refactor some points in our code regarding routing.
We have a list view and a detail view in our application. The detail view will be shown as a pop-up on top of the list view, so in order not to initialize all list controller logic again and again, we have defined our detail view as a child view-state. It looks like this:
$stateProvider
.state('list',
{
url: "/",
template: '<list-directive></list-directive>'
})
.state('list.detail',
{
url: "/detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
It actually works as expected. When i open a detail view from a list view, the list view (i mean the controller) does not run again, and when i close the detail view, the list view remains.
But now we would also like to call the detail view DIRECTLY, without revoking the parent. Currently when i directly call the detail state, the parent controller runs also.. How can i achieve this?
Is a parent - child relationship not a appropriate one for our scenario?
According ui-router doc :
Child states can be used to drill down from a more general feature to
more specific one, or to implement a master/detail pattern. (...) The child state’s view usually renders inside a viewport that the parent state created. This is referred to as Nested Views (...) These substates share the common parent state contacts and inherit data and behavior from the parent.
So I think you should use it if you don't want these behaviour .
It did work like this:
$stateProvider
.state('list',
{
url: "/",
template: '<list-directive></list-directive>'
})
.state('detail',
{
url: "/detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
.state('list.detail',
{
url: "detail/{item_id}",
template: '<detail-directive></detail-directive>'
})
But the angular is very fragile.. and behaves very different with very small changes.. the slashes are the problem.
Child state list.detail does not have a leading slash in its URL. But our new state detail (which has exctly the same content as the list.detail) should have a leading slash so it can be called directly. So We have the same state both as child and as parent.
Now when we call directly the URL "host/detail/5" we go to detail view without revoking the parent. And we we call ui-sref=".detail(5)" we go to child state (list.detail) within the parent

Disable URL change with UI-Router in Angular?

Is it possible to configure ui-router in an Angular 1.5 application to completely ignore the address bar?
Desired behaviour is something like this:
Navigating between states via UI does not change the URL
Changing the URL does not cause navigation between states, only UI or programmatic actions do (clicking the buttons, etc.)
Hitting F5 restarts the application from "default" controller
Original URL (especially query parameters) must still be accessible
UPDATE: I am creating an application with a very specific use case, not a website. Ordinary web browsing practices do not apply here. Please accept the requirements as they are, even if it may seem to contradict common sense.
Agreed that the stated approach is not good if we consider classic web app.
I tried to create a sample application with your requirement in mind.
Here is how my router configuration(router.config.js) file looks like in my fruits app:
.state('start', {
url: '/',
templateUrl: '../app/start/start.html',
controller: 'startCtrl as vm',
})
.state('fruits', {
templateUrl: '../app/fruits/fruitshtml',
controller: 'fruitsCtrl as vm',
})
.state('fruits.details', {
params: {
fruitId: undefined
},
templateUrl: '../app/fruits/fruitdetails.html',
controller: 'fruitDetailsCtrl as vm'
})
Explanation of States:
start:
url / is entry point of my application. If url is /, start state will be loaded.
fruits:
This state shows list of fruits in my app. Note that there is no url defined for this state. So, if we go to this state, url won't change (State will change, but url won't).
fruits.details:
This state should show detail of a fruit with id fruitId. Notice we have passed fruitId in params. params is used to pass parameters without using the url! So, passing of parameters is sorted. I can write ui-sref="fruit.details({ fruitId: my-fruit-id })" to navigate to fruit.details state and show details of my fruit with fruitId my-fruit-id.
As you might have already got it, the main idea is to use states as means of navigation.
Does my app pass your points?
Navigating between states via UI does not change the URL
Changing the URL does not cause navigation between states, only UI or programmatic actions do (clicking the buttons, etc.)
Hitting F5 restarts the application from "default" controller
Original URL (especially query parameters) must still be accessible
->
pass: as we haven't defined url for states
pass: changing url will change to state to start. The app will not take user to any different state, but it does changes state to start or we could say our default state.
pass: refresh will take you to start state
pass: if you write start in your url, you app will got start state.
Possible work around for the 2nd point, which is not passed completely: We can write code to check the url (in startCtrl controller) passed. If url contains extra things appended to /start, go to previous state. If url is 'start' continue loading start page.
Update:
As pointed by OP #Impworks, solution for 2nd point is also passed if we set url of our start state to /. This way, if we append any string to url, the state won't change.

Using UI-Router to split up parts of a big data entry form

I have a big data entry form that's defined programmatically.
I'm modeling it with a PlanController.
On launch, the PlanController loads the plan, its sections, and fields.
The PlanController's template shows:
the plan header information
a navbar loaded from the plan's sections, like this:
li.nav-item(ng-repeat="section in plan.sections | filter:{'active': '1'}" ui-sref-active="active")
a.nav-link(ui-sref="plan.section({idSection: section.idSectionDef})" ) {{sexion.title}}
This is all working great.
Note: Since I don't know what sections there are beforehand, or what the fields in them are, I decided to use a ui-router child route to display the information section information.
This where I run into problems
All the Plan information is loaded in the parent Controller. I'm not sure how to bind the child information in the child view to the parent to the parent.
First Try - no child controller
I've tried declare the child (section) state without a controller, and try to access the section information directly from the PlanController:
.state('plan.section', {
url: '/section/{idSection}',
templateUrl: 'app/plan/planSection.html'
})
but in binding the section fields in the child state's template, planSection.html :
div.col-md-4.col-sm-12
ilp-field(ng-repeat="fld in getFields($stateParams.idSection)| filter:{ layoutColumn: 10, idSectionDef: $stateParams.idSection}" datafield="fld" )
div.col-md-8.col-sm-12
ilp-field(ng-repeat="fld in getFields($stateParams.idSection) | filter:{ layoutColumn: 20}" datafield="fld" onchangecallback="checkilp")
I get the fields for the first section ($scope.getFields takes a section id and returns a list of fields that belong to that section).
When I select one of the other child sections, the url is showing the change of states, but the fields are not changing.
Second Try - child controller
I originally tried declaring the child state with its own controller:
.state('plan.section', {
url: '/section/{idSection}',
templateUrl: 'app/plan/planSection.html',
controller: 'planSectionController'
})
but I don't know how to get the list of fields from the parent to the child control, and when I try to bind to:
$state.$current.parent
as in
ilp-field(ng-repeat="fld in $state.$current.parent.getFields($stateParams.idSection)| filter:{ layoutColumn: 10, idSectionDef: $stateParams.idSection}" datafield="fld" )
I get an error that $state.$current.parent is not accessible.
Would welcome some guidance on the right design pattern to use here with ui-router

Angular UI Router with multiple optional parameters

I have a state route as follows :
.state("hospitals",
{
url: "/hospitals/:categoryName/:providerName",
templateUrl: "app/components/hospital/views/hospitals.html"
})
In general,hospitals pages have all the hospitals with all categories and all provider(hospital) group. In addition, from home page we can navigate to the hospitals page using the category or provider which will show hospitals based on category and provider group respectively. For these, I navigate using the following directives inside anchor tag.
ui-sref="hospitals({categoryName: category.KeyName})"
ui-sref="hospitals({providerName: provider.KeyName})"
So, this introduces a dirty slash when navigating with providerName.
hospitals//somehopitalNme
It is fine with category.
coupons/heart/
Once inside the hospitals page, I can search using category or providerName and get the list of hospitals. I was thinking of modifying the url in these searches(using ui-sref for the same page) since it makes sense to show the url matching to the search. I land in a trouble here. The search is a common search and can match either providerName or category or even a text as well. In this case, how can I navigate or how to use s-ref. I am fine with not using ui-sref and just going to the api or getting the data and updating the results which is easily achievable. However, I prefer the change of url since it shows what we are looking for.
So, now I understand that the state route which I provided is not suitable.
Please provide me an approach as to how to do it.
I was thinking of adding another state called "search" for this, which makes sense. But the templateUrl for this will be the same. Please suggest if I am in right direction or not.
Check this for detailed explanation and working example
Angular UI-Router more Optional Parameters in one State
we can use so called squash setting of the params : {} object:
.state("hospitals",
{
url: "/hospitals/:categoryName/:providerName",
templateUrl: "app/components/hospital/views/hospitals.html",
params: {
providerName: { squash: true },
categoryName: { squash: true },
},
})
Also check these:
Angular js - route-ui add default parmeter
Option url path UI-Router

Updating URL in Angular JS without re-rendering view

I'm building a dashboard system in AngularJS and I'm running into an issue with setting the url via $location.path
In our dashboard, we have a bunch of widgets. Each shows a larger maximized view when you click on it. We are trying to setup deep linking to allow users to link to a dashboard with a widget maximized.
Currently, we have 2 routes that look like /dashboard/:dashboardId and /dashboard/:dashboardId/:maximizedWidgetId
When a user maximizes a widget, we update the url using $location.path, but this is causing the view to re-render. Since we have all of the data, we don't want to reload the whole view, we just want to update the URL. Is there a way to set the url without causing the view to re-render?
HTML5Mode is set to true.
In fact, a view will be rendered everytime you change a url. Thats how $routeProvider works in Angular but you can pass maximizeWidgetId as a querystring which does not re-render a view.
App.config(function($routeProvider) {
$routeProvider.when('/dashboard/:dashboardId', {reloadOnSearch: false});
});
When you click a widget to maximize:
Maximum This Widget
or
$location.search('maximizeWidgetId', 1);
The URL in addressbar would change to http://app.com/dashboard/1?maximizeWidgetId=1
You can even watch when search changes in the URL (from one widget to another)
$scope.$on('$routeUpdate', function(scope, next, current) {
// Minimize the current widget and maximize the new one
});
You can set the reloadOnSearch property of $routeProvider to false.
Possible duplicate question : Can you change a path without reloading the controller in AngularJS?
Regards
For those who need change full path() without controllers reload
Here is plugin: https://github.com/anglibs/angular-location-update
Usage:
$location.update_path('/notes/1');
I realize this is an old question, but since it took me a good day and a half to find the answer, so here goes.
You do not need to convert your path into query strings if you use angular-ui-router.
Currently, due to what may be considered as a bug, setting reloadOnSearch: false on a state will result in being able to change the route without reloading the view. The GitHub user lmessinger was even kind enough to provide a demo of it. You can find the link from his comment linked above.
Basically all you need to do is:
Use ui-router instead of ngRoute
In your states, declare the ones you wish with reloadOnSearch: false
In my app, I have an category listing view, from which you can get to another category using a state like this:
$stateProvider.state('articles.list', {
url: '{categorySlug}',
templateUrl: 'partials/article-list.html',
controller: 'ArticleListCtrl',
reloadOnSearch: false
});
That's it. Hope this helps!
We're using Angular UI Router instead of built-in routes for a similar scenario. It doesn't seem to re-instantiate the controller and re-render the entire view.
How I've implemented it:
(my solution mostly for cases when you need to change whole route, not sub-parts)
I have page with menu (menuPage) and data should not be cleaned on navigation (there is a lot of inputs on each page and user will be very very unhappy if data will disappear accidentally).
turn off $routeProvider
in mainPage controller add two divs with custom directive attribute - each directive contains only 'templateUrl' and 'scope: true'
<div ng-show="tab=='tab_name'" data-tab_name-page></div>
mainPage controller contains lines to simulate routing:
if (!$scope.tab && $location.path()) {
$scope.tab = $location.path().substr(1);
}
$scope.setTab = function(tab) {
$scope.tab = tab;
$location.path('/'+tab);
};
That's all. Little bit ugly to have separate directive for each page, but usage of dynamic templateUrl (as function) in directive provokes re-rendering of page (and loosing data of inputs).
If I understood your question right, you want to,
Maximize the widget when the user is on /dashboard/:dashboardId and he maximizes the widget.
You want the user to have the ability to come back to /dashboard/:dashboardId/:maximizedWidgetId and still see the widget maximized.
You can configure only the first route in the routerConfig and use RouteParams to identify if the maximized widget is passed in the params in the controller of this configured route and maximize the one passed as the param. If the user is maximizing it the first time, share the url to this maximized view with the maximizedWidgetId on the UI.
As long as you use $location(which is just a wrapper over native location object) to update the path it will refresh the view.
I have an idea to use
window.history.replaceState('Object', 'Title', '/new-url');
If you do this and a digest cycle happens it will completely mangle things up. However if you set it back to the correct url that angular expects it's ok. So in theory you could store the correct url that angular expects and reset it just before you know a digest fires.
I've not tested this though.
Below code will let you change url without redirection such as: http://localhost/#/691?foo?bar?blabla
for(var i=0;i<=1000;i++) $routeProvider.when('/'+i, {templateUrl: "tabPages/"+i+".html",reloadOnSearch: false});
But when you change to http://localhost/#/692, you will be redirected.

Resources