ui-router $urlRouterProvider wait until all states loaded - angularjs

I'm trying to dynamically load a bunch of states, by merging them from some disperse files.
I'm collecting all of them with $http.get and then add all with $stateProvider.state(name, config).
All ok here.
The problem is that if I enter another URL, besides the root URL "/", that same URL is never resolved to the correct state.
It seems that, if i load the app from the root state and navigate from there, the $urlRouterProvider can match with all the loaded states, but, if i try to enter the app from a child state, for example "/#/anotherpage", it cannot match any url/state and it fallback to .otherwise('/').
It's like if it tries to resolve the URL without waiting for all the states to be loaded.
I'm using $urlRouterProvider.deferIntercept() to try to stop it from continue, and after the configuration, i just enable again the sync.
app.config(configure).run(['$urlRouter', function($urlRouter){
$urlRouter.sync();
$urlRouter.listen();
}]);
How do i make sure that $urlRouterProvider waits until all the states are loaded during .config(), and then try to match the correct state?
Thanks.

I'm not really sure that what i had is related but i'll post it : i was creating some object in a config phase. The object configuration was overridable so i couldn't built the state in the config phase of my provider. So i moved the $stateProvider calls and $urlRouterProviders calls in the $get of my provider.
If i tried to aim for one of the generated state it didn't work when loading the page the 1st time or refreshing. I had to use a "module.run(myProvider){}" to force instantiation of my provider to make it works. My guess is that my app.run was running before $urlRouterProvider resolves anything.
Another solution may be to use defer the bootstrap of your application removing ng-app and using angular.bootstrap when you're ready.

Related

ui-router for manual parsing URL without using views

I want to watch a location and run a function when the location changes.
I also want to be able to easily change locations by running a function.
I want to be able to use the browser back buttons.
Sound like a good mission for ngRoute or ui-router
No.
I don't need views, templates, and controller.
I want the power of ui-router and ngRoute for parsing URL's and get the stateParams.
In other words: How to use routes in angular without using views, templates, or controllers?
Sounds what you need is just the $location service. You can use it without ngRoute or ui-route.
The $location Service. What does it do?
The $location service parses the URL in the browser address bar (based on window.location) and makes the URL available to your application. Changes to the URL in the address bar are reflected into the $location service and changes to $location are reflected into the browser address bar.
The $location service:
Exposes the current URL in the browser address bar, so you can
Watch and observe the URL.
Change the URL.
Maintains synchronization between itself and the browser's URL when the user
Changes the address in the browser's address bar.
Clicks the back or forward button in the browser (or clicks a History link).
Clicks on a link in the page.
Represents the URL object as a set of methods (protocol, host, port, path, search, hash).
-- AngularJS Developer Guide -- Using $location
Events
$locationChangeStart
Broadcasted before a URL will change.
$locationChangeSuccess
Broadcasted after a URL was changed.
-- AngularJS $location Service API Reference
You can use the $location service without either the Angular router or the Angular-UI router. In fact you can use it if you want to roll your own router.
I am not sure what is your setup is, but if you control all the routes and they are rather limited, then you can try to add the "abstract" states, they don't need a view or a controller. I think that by the way this is not required for ordinary states either, but not 100% sure.
Perhaps if you define the abstract route for the top of your application, then you will get events for all "theoretical" children.
You can find an example of abstract state here
$stateProvider
.state('contacts', {
abstract: true,
url: '/contacts',
})
Now if you will go to /contacts you should get the event of stateChange, and I think you will get it if you will go to /contacts/something as well. In the worst case you might define you whole application as a tree of this parent/child states that are all abstract.
To handle the event you need to do this:
$rootScope.$on('$stateChangeStart', function(event, toState){
var greeting = toState.data.customData1 + " " + toState.data.customData2;
console.log(greeting);

Angularjs: use config only for one controller

On one page I load content via ajax according to user picks (filters), to ensure that loaded content stays in place if user reloads or lands on the page, I put the picked filters into the url query string. Since I load the content via ajax on this particular page I don't need to reload the entire page every time a new filter is picked by the user, so I prevent browser to react on url change with the following config:
app.config(['$locationProvider', function($locationProvider) {
$locationProvider.html5Mode(true);
}]);
However this affects the entire app and prevents all other pages from reloading on url change, the behavior I don't want. How can I make this configuration to affect only one particular controller within my app?
If your goal is to prevent reloading the page when the query string changes, html5Mode is entirely the wrong tool for the job. You want reloadOnSearch: false which can be applied globally or to individual routes:
$routeProvider
.when('/foo', {
controller: 'fooCtrl',
templateUrl: 'foo.html',
reloadOnSearch: false
},
...
);
https://docs.angularjs.org/api/ngRoute/provider/$routeProvider
From Angular's documentation on $locationProvider, maybe the cause of that behavior is by design:
rewriteLinks - {boolean} - (default: true) When html5Mode is enabled,
enables/disables url rewriting for relative links.
If your app is reacting to the url to make a change as a sort of RESTful api I would recommend using ngRoute or even better uiRouter.
Hope that helps.
This is a tricky situation, and you might not like my suggestion; heck I don't even like this suggestion because it breaks the whole awesomeness of a single page application.
But what you could do, is to create a separate html file (lets call it pick-filters.html). On that new html file, have a new ng-app and therefore a separate app.js file for this particular page. In this new app.js file (lets call it pick-filters-app.js), you can use the app.config snippet that you have shown here. This should fix your problem of all pages not reloading because only pick-filters.html is referencing pick-filters-app.js which has this config snippet.

Remove an already-registered $state

I was surprised to learn that ui-router shares state across modules.
That's ok, but for our development environment I'd like to clear all the application states and start from scratch.
It's not convenient to avoid defining them in the first case just for our test environment, because the state definition is mixed in with all the other application loading, and we'd like to be sure that the application dependencies are describe the same way in all environments.
So can I clear, or remove one-by-one, the already defined states?
.config( function($stateProvider) {
// TODO: get rid of $state definitions from the app, we don't want them here
}
With angular-ui-router starting from Version 1.0.0 (currently in rc1 version) it is now possible to properly remove states.
Inject $stateRegistry (runtime) or $stateRegistryProvider (config) and call the deregister() method.
Here the detailed API, implemented with this commit
This is not possible. ui-router uses ng-router and ng-router does not expose the routes until after the provider is compiled. Meaning you wouldn't be able to remove states/routes until the app is configured. Which I don't think they will accept a PR.
Edit:
In response to the comment made. The routes objects used by the $routerProvider is available and you can remove routes.
https://github.com/angular/angular.js/blob/master/src/ngRoute/route.js#L451
delete $route.routes['/'];
But the states object used by the $stateProvider is not available.
https://github.com/angular-ui/ui-router/blob/a7d25c6/src/state.js
So ui-router would need to be modified to be able to remove states after config. OR ng-router would need to be modified to be able to remove routes during config.

ngRoute - re-route to current location path

Is it possible with ngRoute to route to the current path? Essentially, I have a scenario where my app will route to a controller/view, then after a little bit of time an event handler can be executed in another part of the application which changes a global object, and the same controller/view should be executed again. I tried just routing to the same path (using location.path), but it appears that ngRoute ignores the request it the apps is currently on that path.
Going by the documentation you can use the $route.reload() method:
Causes $route service to reload the current route even if $location hasn't changed.
As a result of that, ngView creates new scope and reinstantiates the controller.

Angular reload current route and reload the current template

When a use visits a private page unauthorized, say profile, my backend 302 redirects to a controller action that serves up the login partial in place of the profile partial. Since it 302 redirects to an action that returns a partial, the url address bar doesn't change from the page the user was trying to access ("/profile").
I was going to "fix" that but actually I think it makes a good user experience instead of dealing with return urls as query params.
The idea is once they log in I just want to reload the current route aka do a GET request for the profile partial via "/profile" and switch it back in instead of the login partial.
However, I can't get this "reload current route" to work. I tried all of the following:
$location.$$compose();
$location.absUrl($location.path());
$location.url($location.path());
$location.path($location.path())
$route.reload();
But none work. $route.reload() seems to be the definite way but it also doesn't work. It goes through the route cycle, reinstantiates the controller, but does not do GET request to reload the template
The only thing that works is a hard refresh via location.reload() but that is not ideal.
How can I force angular to reload the template for the current route?
Ok I found the solution provided by lgalfaso on Github (exact paste):
Templates are
cached, if a user does not have the permissions to be in a page, then
this check should be done before it reaches the controller or after,
within the controller, but not on the template retrieval
If this is the way you want to follow, then you need to remove the
template from the $templateCache before you call reload
So that worked for me because login template actually gets cached as the template the user was trying to access. So removing it and letting angular re-fetch the correct one for the current route worked like a charm.
var currentPageTemplate = $route.current.templateUrl;
$templateCache.remove(currentPageTemplate);
$route.reload();
I have noticed that $route.reload() method re-instantiates everything that is setup on your $routeProvider.when("/someUrl",{controller:'SomeController',templateUrl:'SomeView.html'}) template,controller and/or any resolved promises you may have passed within the .when() method.
Therefore; when you build your app, if you want $route.reload() to reload all the page and re-instantiate controllers you must put everything under your <div ng-view></div> container and include any menus or footers inside the templateUrl file.

Resources