Lets say, there is application, that can be run for different units, with different names. Before, when user select another unit, we just reinstating controller, and load new data for new unit, this changes was not reflected on url. But, now we need to add unit name to the url.
For example we have following state:
$stateProvider.state('management', {
url: '/management',
templateUrl: 'features/management/templates/management.html'
});
And in browser we can see:
http://localhost:84/#/management
Now, when we go to that state, I need browser to show something like this:
http://localhost:84/#/unitName/management
For example we have unit1, unit2, unit3, and when user switch between them, url will be changed to
http://localhost:84/#/unit1/management,
http://localhost:84/#/unit2/management,
http://localhost:84/#/unit3/management
and so on.
I can get unitName from service, for example unitService.js that will return me a unit name as a string. But I have no idea how to add this string to url in browser. This unit name should be added to all routes, but without changing them.
Can I make first function, that will add unit name to browser url after all data was loaded, and second function, that will remove that string when angular starts to searching for state by url, so instead of unit1/management it will look for /management state?
Is it possible? Thank you in advance.
You can use notify: false.
Route declaration
$stateProvider.state('management', {
url: '/:unit/management',
templateUrl: 'features/management/templates/management.html'
});
State go example
$state.go('management', {unit: 'unit1'}, {notify: false})
Related
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.
I have a state that is the entry point for my application. However, the entry page requires a date parameter in order to show a list. It is configured to use the current date if no date was provided in the URL.
Technically, everything works. The default value is passed to the controller and the controller is able to use it to load some data, but it won't show the date in the URL.
This is the state configuration:
$stateProvider.state('openJobs', {
controller: 'openJobsCtrl as openJobs',
params: {
shippingDate: {
value: getFormattedCurrentDate(),
squash: false
}
},
templateUrl: 'openJobs.html',
url: '/openJobs/:shippingDate'
});
I have created a working plunkr to demonstrate the behaviour (run in new window to see URL changes). The default value will show in the URL if I create an ui-sref link to openJobs. However, if someone were to open the page directly by entering http://host.com/openJobs/ the default value will be used in the background, but not shown in the URL.
How can I achieve that, no matter how the state was opened, the default value will show in the URL?
You can redirect the incoming path using urlRouterProvider with default date appended to it. urlRouterProvider will update the browser location.
$urlRouterProvider.when("/openJobs/", function() {
return "/openJobs/" + getFormattedCurrentDate();
});
If a state is matched against state name (through ui-sref or $state.go()), browser location will be updated with url path.
Whereas, if a state is matched against url path, the browser location will not be updated because it already matched the url. That's why you need to use $urlRouterProvider in your case to append the date.
I am facing a url structure problem using ui-router in AngularJS. I want to have first level SEO friendly url structure like this:
https://people-profile.com/mike-david-tringe
So I can grab the SEO name "mike-david-tringe" via stateParam and use it to find data in database and populate the page.
The $stateProvider has code like this:
$stateProvider
.state('people', {
url: '/:nameUrl',
templateUrl: 'app/frontend/page.tmpl.html',
params: {
nameUrl: {squash: true},
},
controller: "PageController",
controllerAs: 'vm'
})
.state('admin', {
url:'/admin/:userId',
templateUrl:'app/frontend/admin/admin.html',
controller:'AdminController',
controllerAs: 'admin'
})
With above code, I can have https://people-profile.com/mike-david-tringe working with nameUrl = mike-david-tringe and I got SEO friendly first level url link. mike-david-tringe is SEO friendly and most important keywords beside the domain name.
But with this structure, https://people-profile.com/admin/ or https://people-profile.com/login/ will not work now. Since my controller try to grab admin as nameUrl and looking for data. And admin is not a nameUrl so my database will return null, the app will fail.
In short, stateParam nameUrl will grab anything after "/" so the url setting will think admin and login is :nameUrl but in fact, it is not.
So how do I structure my app ui-router structure to have SEO friendly url like https://people-profile.com/mike-david-tringe but when url is https://people-profile.com/admin/, it will load admin.html template and use AdminController instead as I defined in $stateProvider?
All you need to do is swap the order of them. The router will check in order of definition, so if /:nameUrl comes before /admin it will trigger first. But if you put /:nameUrl last then it will trigger on any url that hasn't already triggered something above.
A word of warning however. Moving between two urls that trigger the same state (like two urls that both hit /:nameUrl in your case) will not reload the controllers on the page. Only changing state will do that. There are options to change this behaviour, but it has always been very buggy for me.
I have a state defined something like this:
$stateProvider.state("app.search", {
url: "/search?q",
templateUrl: "App/Search.html",
controller: "SearchController as vm",
});
When I want to navigate to the search page and specify a search term, I can do something like this:
$location.path("search").search({ q: "stuff" });
Which effective resolves to a url along the lines of #/search?q=stuff. If I then change the search term to "things", the search page correctly reactivates and searches as expected.
However, I'd like to be able to specify "random" querystring parameters which have not been defined in my url, and then have those parameters also reload the state. (Note: this is why I'm using $location.path instead of $state.go to change the URL). So for example, if I was searching for food, my URL may be:
#/search?q=stuff&type=food
And then I may be filtering on spice levels, which may change the URL to:
#/search?q=stuff&type=food&spice=medium
(etc).
The problem though, is that since I haven't defined all the other query string parameters (type and spice in this example), ui-router doesn't reload my page.
I can't use $state.go with { reload: true } because it ignores the unspecified parameters.
Is there a way I can trigger a page reload using "unspecified" querystring parameters? This post suggests I can make the route /search* but that doesn't appear to work for me.
I haven't found a nice way to do this yet with ui-router. If you're willing to sacrifice your integrity a little bit you could utilize an alternate format for your query params. You can use a single http url query param object and list all your criteria using a custom format.
e.g.
/search/q?stuff:type=food:spice=cinnamon
The route setup would look like:
$stateProvider.state("app.search", {
url: "/search?q",
templateUrl: "App/Search.html",
controller: "SearchController as vm",
});
and to navigate with the extra parameters:
$state.go('app.dashboard', { q: 'stuff:type=dessert:spice=cinnamon'});
to access the parameters on the route:
$state.params.q
You can then just parse out the 'stuff' value and then each name/value pair that was passed in.
PS> I'm not sure whether the colon (:) separator is valid but it was used to illustrate the proposed idea.
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.