UI Router event on navigate to self page - angularjs

I'm using ui-router for my page routing in my AngularJS app. I'm encountering an issue where if I click a hyperlink on a page that refers to the page/route I'm already on (i.e. self), nothing will happen.
For example, if you're scrolled halfway down a page and you click a hyperlink that refers to the current page, it will seem as though the link doesn't load, when in actual fact nothing needs to be loaded as the user is already on the right page.
The goal in this situation is to have the viewport moved to the top of the page, but I cannot get any event from ui-router for this event. I've tried $stateChangeStart and $stateChangeSuccess, but as no route change occurs, these do not fire.
My urls are of the form http://localhost:3000/projects/myproject and ui-router is configured as follows:
state('app.projects', {
url: '/projects/:projectname',
templateUrl: 'projects.html',
controller: 'ProjectsCtrl'
}).
Without adding extra logic in the controller (which would obfuscate the natural href="projects/myproject" format), what can be done to achieve this?

Without controller:
<a ui-sref="stateName({reload: true})" target="_blank">Link Text</a>
With controller:
$state.go($state.name, {}, {reload: true});

You can simply use # at the end of the link. href="projects/myproject#"

Related

Angular JS Scrolling to an element in another page

I am building a SPA using Angular 1.4 and Grails. In my home page I have 3 sections namely section1, section2 and section3 with the same ID. I have a dropdown menu on the top and I have used the scroll-to plugin to scroll to the respective sections on click.
<li ><a href scroll-to="section1">History</a></li>
My Problem is that when I move to another route, the dropdown menu will still be at the top and I am not sure how to get this to scroll to that element in the home route from another route. This is what I have tried.
<li >Economic Weight</li>
And in the route config I did this
when('/home/:section', {
templateUrl: '/euribron/js/app/templates/home.html',
controller: 'HomeController'
}).
and in controller I did this
if($routeParams.section) {
var element = document.getElementById($routeParams.section);
smoothScroll(element);
}
But this does not work when I am on the same route and it does not seem elegant. Is there an easier way to achieve this in an efficient way.
Instead of scroll-to plugin, you can use $anchorScroll service to manage autoscrolling. Please see this post's answer How to handle anchor hash linking in AngularJS. Basically, you need to set $location.hash() to the section's id that you can get from $routeParams and call $anchorScroll() function to handle the rest. Use different id for the sections.

ui-router renders template before state changes

I have a nested state structure like this:
$stateProvider
.state('main', {
url: '',
abstract: true,
templateUrl: ...
controller: mainController,
resolve: {
Resolve1: ...
Resolve2: ...
}
})
.state('main.state1', {
url: '^/state1/:id/',
templateUrl: ....
controller: state1Controller,
resolve: {
Resolve11: ...
Resolve22: ...
},
})
.state('main.state2', {
....
From what I am seeing, when you are at state main.state1 and you navigate to the same state with another id parameter, the template for main.state1 is rendered fine but the template for main (the parent) is rendered before the state changes (I guess it doesn't wait for the data resolution of main.state1). This results to the view being rendered with the wrong data (more specifically, the state parameters that I use for generating links are wrong).
My mainController has the $stateParams and $state injected to it and that's where I get the data from.
Did anyone notice this before? Is this by design or is it a bug?
Is there any way to update the parent view with the latest data?
However, I would expect ui-router to wait for all data resolutions until it starts rendering the views (even the parent). I don't know if I am missing something here, but this is my understanding of the problem so far...
UPDATE:
I see now that a function that is involved in the view rendering (with interpolation) gets called many times, first with the old values and then with the new values. But the final result that I see on screen is using the initial data that were used when I first entered the 'main.*' state. This is SO weird.
UPDATE 2:
I have found that my links where not updated ONLY when using ui-sref (note that I was using parameters in the state href, e.g. ui-sref="main.state1({id:{{getId()}}})"). When switched to ng-href everything worked as expected.
I have no idea why, but that fixed the problem.
The odd thing was that the ui-sref was evaluated, but the link was not updated.
Go figure...
UPDATE 3:
This solution in turn caused other problems. Then the first click reloaded the application... So this is not the best fix...
The "solution" was to use ng-click with $state.go(), instead of links.
This appears to be a bug in ui-router.
ui-router transitions "through" the parent state to the child. That means main will be triggered, then main.state1. I ran into this myself where I was getting duplicate listeners and HTML DOM elements - main is actually running AT THE SAME TIME as main.state1. This behavior is by design, and AFAIK your only option is to design around it.

Angular - trigger unsavedChanges directive when user clicks on a non-submit button

How do I trigger the angular-unsavedChanges directive to check to see if a form is dirty when a user clicks on a button in a form that is not a submit button? I want a message to pop-up if the user clicks on a button that would navigate them to a different page, if changes have been made to the current form. It seems to work on a refresh and a link to a different page, but not a button that will be loading a different template. TIA.
Without sample code, I can't be sure, but I think that since the directive you are using specifically binds to the $locationChangeStart event, you could try the following :
configure your $routeProvider to monitor url changes ( I suppose it is already what you are doing with your SPA, but correct me if I'm wrong)
$routeProvider
.when('/otherPage', {
templateUrl: 'partials/myOtherPage.html',
controller: 'OtherCtrl',
})
Use you the ng-click on your button to call the $location service (don't forget to pass it to your controller)
$scope.navigateToOtherPage = function() {
$location.path('/otherPage');
}
Since $location fires the $locationChangeStart event right before changing the url, your directive should be able to capture it and proceed with its expected behaviour.

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.

Angularjs - using $routing - when I manually put "#/section/ + myId" , it resets my page

So I am trying to implement routing in my app. Here is a sample jsFiddle of what I am trying to do: http://jsfiddle.net/GeorgiAngelov/9yN3Z/114/
So far everything is working fine and as I am moving through the sections, my routing changes and everything is fine.
The problem is the following: when I manually type the section id in my url bar like this my local host : /section/s-3 I get redirected to s-1 and my page refreshes and I loose all of the sections and roots I have added so far. Sometimes it works, and I can switch between sections like that, but sometimes it refreshes, and it happens when I click enter twice.
It's an odd problem and I am not certain on how can I debug it. This is my actuall routing service.
app.config(function ($routeProvider) {
$routeProvider
.when('/', {
redirectTo: '/section/1',
templateUrl: '/tpl.html',
})
.when('/section/:sectionId', {
templateUrl: '/tpl.html',
})
.otherwise({
redirectTo: '/'
});
});
I tested your app on chrome and on firefox. Using chrome, if you select the url and type enter (without changing it) it reloads the page, so hitting enter twice will always refresh the page. Using firefox, this does not happen, so apparently you don't have this problem on FF (although the user can press the refresh button directly).
If you refresh the page, you obviously lose what you have inserted. You need to persist the information you want to preserve between refreshes. If you don't want to send this to your server, an alternative is to use html5 local storage. You can see an example here:
http://todomvc.com/architecture-examples/angularjs/#/
I'we watched your application and from my point of view you're using an odd way to add DOM object to your view, kinda like jquery templating.
Actually each times you click inside a section you're using a different scope and adding a root in a scope it's not visible to the others: instead you should use a service shared between the controllers, this way only the service contains all the sections and all the roots: angularjs services
Later I'will update your jsfiddle, hope this helps for now.

Resources