AngularUI Router - scroll to element on state change - angularjs

I have searched on StackOverflow for the last five hours and none of the related answers quite solve my problem. I have an UI-Router state that loads a long list of messages generated from a custom directive. This page is linked too in many places pointing to a different message. I want to scroll to the currently selected message.
I can get this to work using $anchorScroll if I surround the call with a $timeout. $timeout(function(){$anchorScroll()};) but if the $timeout is not there a call to $anchorScroll does nothing since the View has not completely loaded.
Here is most of the relevant code.
<message id='message{{message.id}}'
ng-repeat='message in messages'
message='message'
ng-class="{'current':current == 'message{{message.id}}'}" >
</message>
In the controller I set current to $scope.current = $location.hash(). This all works.
If I load the page like #/messages#message100 directly the page will correctly scroll. However, if from a different view I use the a link such as this:
<button ui-sref="message-state({'#':'message{{message.id}}'})>
Go To Message {{message.id}}
</button>
The page will not automatically scroll to the correct anchor since the message list has not been made yet. But by putting the call to $anchorScroll() in a $timeout I can make the page scroll.
I don't like using $timeout for this purpose. I know I am not supposed to manipulate the DOM in a controller like this.
I have tried registering the call to $anchorScroll() with many of the $stateProvider events such as:
$state.$on('$viewContentLoaded', function(event) {
$anchorScroll();
});
But even at the time the $viewContentLoaded fires the message list does not exist in the DOM and the page does not scroll.
IWhat is the best way to make the UI-Router scroll based on the $location.hash().

Even I was facing a similar situation and after days of try and error, I came up with this.
In ui router add an id parameter to the state on which you want to enable the scroll.
$stateProvider.state('index', {
url: '/',
params: {
id: null
}
})
Then in the html use ui-sref
<li><a ui-sref="index({id: 'about-us'})" >About Us</a></li>
At last in the app.run module detect the state change using
$rootScope.$on('$viewContentLoaded', function(event){
if ($state.current.name == 'index') {
if($stateParams.id) {
$anchorScroll.yOffset = 150;
$location.hash($stateParams.id);
$timeout(function(){$anchorScroll()}, 1000);
}
}
});
Hope this helps. Would do a plunkr if needed.

Related

Angularjs UI-Router, no state change on hash change

We have an accordion on the page which sets hashes for the topics, to allow deep linking. The page runs on UI-Router in HTML5Mode. When clicking on the topics, Ui-Router reloads the resolves.
How can I stop this??
I tried:
reloadOnsearch = false in the state definition, which works great on search params but still reloads on hash change
if( $transition$.$to().name == $transition$.$from().name) {
$transition$.abort() // or return false; // or preventDefault()
}
Which works, but doesn't put the hash into the address, which is obviously needed.
How can I stop UI-Router from reloading the resolves and firing its state change events?
Update with more info:
I tried both,
<a ng-click="toggle();">
with $location.hash("value")
and
<a href="#value">
with preventDefault() in the controller, which (as expected) didn't made a difference.
Solution (hopefully)
This seems to work, in case someone faces the same problem:
$transitions.onBefore({}, function( transition ) {
if(transition.to().name == transition.from().name) {
const paramsCopy = Object.assign({}, transition.params());
const stateService = transition.router.stateService;
return stateService.target(transition.from(), paramsCopy);
}
})
What I understand from your question is you have a accordian with angular Js and the UI router reloads the page when you try to click on the accordian tabs.
Try replacing href attribute from data-target
<li><a data-target="#switchTabs" data-toggle="tab">Tabs</a></li>

Running ui.router in modal and ui-view

So not certain if this is possible with ui.router, but thought I would add a nice little modal that launches and says welcome to the site, give the user a chance to enter some details and so forth.
Setup is that the base view have two ui-views (mainview and menuview), and I added formview in the modal. I can get it to work so that when the modal opens it loads formview
.state('form-welcome', {
views: {
formview:
{
templateUrl: "/modals/form-welcome",
},
},
//parent:"index"
})
Didn't actually think it would work that easy, but it did, the problem is that as soon as it has loaded, it resets mainview and menuview (and as it is a modal, that means the background goes grey).
I tried to make form-welcome a child of index (the initial view), however, that breaks the whole thing. (index looks as follows)
.state('index', {
url:"/int/index",
views: {
mainview: {
templateUrl: "/pages/index",
controller: "sketchMagController"
},
menuview: {templateUrl: "/pages/top-menu"},
},
})
I can reload all three views (mainview, menuview and formview), and other than a flickering screen its not to bad. But is there a way I can limit state, so that it only changes formview but leaves the other ones alone.
Reason is that I want to change formview through five different screens, and hate flickering pages:)
It seems to me like it should be possible, but I may have missunderstood how it works
UI-router is for changing the application state, not nesting views together. For that purpose you have angular#component.
var app = angular.module('app', []);
app.component('myModal', {
template: '<div ng-show="isShowing">Hello User</div>',
controller: 'modalCtrl'
});
app.controller('modalCtrl', ['$scope', 'modalService', function($scope, modalService) {
//Save showing state in a service (default to false) so you don't popup your modal everytime user visit homepage
$scope.isShowing = modalService.getShowStatus();
$scope.pressButton = function() {
$scope.isShowing = false;
modalService.setShowStatus(false);
}
});
Then using ui-router, declare your index state, with its template as follow
<-- INDEX.HTML -->
<my-modal></my-modal>
<div ui-view='mainview'></div>
<div ui-view='menuview'></div>
The power of ui-router is the ability to replace the ui-views by different template each different state
stateIndex: abstract
stateIndex.stateA: mainview: /home.html
stateIndex.stateB: mainview: /information.html
So ask yourself, will menuview gonna change to different templates in future states? If not, make it a component.
I would try a different approach, and not having this "welcome" modal part of UI router. It doesn't sounds it should be a "state" in an app, where you can navigate to etc.
I would just pop up this welcome modal after your app finished to bootstrap (e.g. in your run() method or after w/e logic you have to start your app), based on your business logic (e.g. show it only one time).
Why not try $uibModal, part of ui-bootstrap? https://angular-ui.github.io/bootstrap/
It sounds like its everything you need, It pretty much has its own state (includes its own view and controller) so piping data in is easy. You can even pass on data captured within the modal with a simple result.then(function(){} . We use it at work and it doesn't reload the state its on.
You'll probably just want to have it be a function that runs automatically in your controller. If you want to limit how often it pops up you can even have some logic for determining when it pops up in the resolve, pass it to your controller and open the modal based on the resolve.
I think the best way to accomplish what you want is to listen on state changed event and fire the modal. The idea is you fire the welcome modal when the first view is opened and then you set variable in localStorage or some service (depends what you want to achive). Here is an example
$rootScope.$on(['$stateChangeSuccess', function(){
var welcomeModalFired = localStorage.get('welcomeModalFired');
if(!welcomeModalFired) {
//fire modal
localStorage.set('welcomeModalFired', true);
}
})

AngularJS won't refresh the page if the link that is clicked is with the same URL as the current page

I noticed this on two of my projects where I'm using AngularJS, so I'm assuming it's an issue with AngularJS.
Let's say I have a button on the menu that says "Register" and takes me to /account/register page. But, if I'm on the /account/register page, clicking the button won't refresh the page. It's like the button is disabled. This is always happening when the link I want to click has the same URL as the current page I'm on. The URLs are simple Link. How can I remove this behavior?
Had the same problem with angular, adding target="_parent" attribute to the link made it work.
For who didn't understand, I have recorded my screen to display this issue.
Basically, there are somethings wrong of Angular.
If we place the navigation outside of the ng-app.
The app has already loaded.
Then we click again to this navigation.
The app will disappear with no reason.
I fixed it by adding target="_parent" or target="_self" attribute to the anchor tag as answered above.
Check more on Angular docs
You can check if the current url/state/hash is "/account/register" ... if yes then use reload method of the $route service.
AngularJs: Reload page
I didn't find any reasonable way to check the path inside view, but this could work:
view:
<a link-href="path/to/same/location">same location</a>
directive:
app.directive('linkHref', ['$location', '$route', function ($location, $route) {
return {
restrict: 'A',
link: function ($scope, $element, $attrs) {
$element.bind('click', function () {
if ($location.$$url == $attrs.activeLink) {
$route.reload();
} else {
$location.path($attrs.activeLink);
});
}
};
}]);
I had the same problem and found that if I removed $location (wasn't using anyways) that was listed in a factory the problem went away.
<a target="_self" href="/xxxxxxx"></a>
will fix this problem

Modal loading spinner not showing up a second time

I'm currently working on a project in angular. I'm trying to show a loading spinner every time the page changes. To accomplish this we are using ngRoute module and listening for routeChangeStart,routeChangeSuccess,routeChangeError events.
Here's the code:
$scope.$on('$routeChangeStart', function (event, param) {
var html = "<div class='panel-body'>"
+ "<div class='col-md-4 col-md-offset-4' style='top: 50%;'>"
+ "<i class='fa fa-spinner fa-spin'></i> Caricamento in corso..."
+ "</div>"
+ "</div>";
that.myModal = $modal.open({
template: html,
backdrop: 'static'
});
});
$scope.$on('$routeChangeSuccess', function (event, param) {
that.myModal.dismiss('nessuna');
});
$scope.$on('$routeChangeError', function (event, param) {
that.myModal.dismiss('nessuna');
});
This works, but only the first time a certain page is changed. I try to explain better: When we are in page X and navigate to page Y the modal is shown and then hidden after page changes. If then i go back to page X and navigate to page Y again the modal spinner is now shown.
When debugging I can see the modal.open() executing, but it's never shown. It looks like angular is somehow delaying the command.
Does anyone know why is this happening? Has anyone encountered this problem before?
About displaying the spinner solution capturing $http requests, here you have some info to get started:
http://lemoncode.net/2013/07/31/angularjs-found-great-solution-to-display-ajax-spinner-loading-widget/
Showing Spinner GIF during $http request in angular
The idea is to intercept a $http request and display the spinner, then on the end request or promise response (error and success) check if there are pending requests, if not hide the spinner.
If you need more info, give me a buzz, I have further developed this and taken some solutions for issues like I don't want to show the spinner for some particular requests.
Whatever solution you keep to catch route change events, I don't understood why your modal doesn't trigger.
Here is a demo in plunker (I used ui-bootstrap to handle modal).
You can use this nice script too called Pace to automatically start a loader based on http request.
Taken from their documentation:
Pace will automatically monitor your ajax requests, event loop lag, document ready state, and elements on your page to decide the progress. On ajax navigation it will begin again!

ngClick on anchor tags causing problems in Angular

I've got ngClick directives on the anchors tags inside my main navigation, to make the menu "disappear" off canvas after clicking it:
<a href="/#/profile" ng-click="showNav = false">
It's functioning all right. However, I recently noticed an error (in the console) that occurs when I'm changing view through those anchor tags:
Infinite $digest
Loop error in component $rootScope
This is how I handles my routes:
$routeProvider.when('/register', {
templateUrl: 'assets/templates/register.html',
controller: 'RegistrationController'
});
Is this because I change view at the same time as I'm setting "showNav" to false? Is this even the right way to go with this?
If you just want menu to disappear - you stay href empty. If you need moving to new controller then set showNav = false in profile controller. So I mean you need something one - either ng-click or href, not both.
tie the showing and hiding of your anchor tag to the url not the user clicking i.e.:
<a ng-if="location.path() != '/profile' href="/#/profile"></a>
in your controller:
function mycontroller($scope,$location) {
$scope.location = $location;
}

Resources