AnchorScroll only works after second click - angularjs

I believe I am experiencing the same issue mentioned here: $anchorScroll and $location only work after second try
I reviewed the plunker that works and I have routing in place, but it is still taking two clicks. I am using ng-route and not ui-router. How can I prevent it taking two clicks to get anchorScroll to work? As the first wants to cause a route to be established versus scrolling to the appropriate anchor.
Here is the click:
<a data-ng-click="gotoRequests()">Requests</a>
Here is the destination:
<div id="pendingrequests"></div>
Here is the function in my controller:
$scope.gotoRequests = function() {
// set the location.hash to the id of
// the element you wish to scroll to.
$location.hash('pendingrequests');
// call $anchorScroll()
$anchorScroll();
};

I was able to solve it using one of the answers here: How to handle anchor hash linking in AngularJS
by creating the following function:
$scope.scrollTo = function(id) {
var old = $location.hash();
$location.hash(id);
$anchorScroll();
//reset to old to keep any additional routing logic from kicking in
$location.hash(old);
};
I would call this as follows:
Yipee
<div id="pendingrequests"></div>

Latest Update
From AngularJS 1.4.0 $anchorScroll allows you to directly pass the id as a parameter without the need to update the URL with the hash.
During click
<div data-ng-click="gotoRequests(pendingrequests)"> </div>
In Controller
$scope.gotoRequests = function(divId) { $anchorScroll(divId); }

I also had the same issue with angular 1 and I solved it using $timeout. Here is an example of how I did it
angular.module('app').controller('MyTestController', ['$scope', '$location', '$anchorScroll', '$timeout', function($scope, $location, $anchorScroll, $timeout) {
function scrollToElement (element, offset){
$timeout(function() {
$anchorScroll.yOffset = offset; // add extra pixels to scroll initially
var old = $location.hash();
$location.hash(element);
$anchorScroll();
$location.hash(old);
});
}
scrollToElement('element ID', 100);
}]);

You need to add $timer for 300 like:
this.gotoBottom = function(scrollId) {
$timeout(function() {
$location.hash(scrollId); $anchorScroll(scrollId);
}, 300);
}

Related

How to open link in new window using angularjs

I am developing system using angularjs with codeigniter.
What I want to do:
There is anchor tag [edit] for every user (user list is shown using ng-repeat)
on click of edit i want to open new window.Opening new window is not an issue. I want to pass user_id to that new widow. So issue is: passing user_id.
When I go to new window, after edit-update done, (edit update is not an issue), I want to refresh the current application (previous widow:from where I switched).
Hope you got my issue(s).
Sample Code:
<div ng-repeat="user in allUsers">
Only displaying : {{user.user_id}}, It is OK.
<a title="Edit in new window" href='javascript:window.open(\"".base_url()."phpcontroller/loadingview/user_id \",\"User Edit\", \"width=\"+screen.width+\",height=\"+screen.height+\",fullscreen=yes,location=no\");' >Test edit</a>
</div>
This HTML/php PAGE is loaded through angularjs. So it is partial, thats why I cant use php functionality(eg. base_url(), php variable) here. How can I give basepath in partial. Is there any way to declare base url globally in app.js or controllers.js, so that I can use it in partials?
Please try to give suggestions, solutions. If you not get my issues clearly, please comment. Thanks.
UPDATE : Question is not full duplicate of any question on stackoverflow.
if you wanted to do it in an angular way you could do something like this
angular.module('myApp', [])
.controller('myController', ['$scope', '$window', function($scope,$window) {
$scope.openWindow = function(greeting) {
$window.open("http://www.google.com", "", "width=640, height=480");
};
}]);
HTML Code
<div ng-controller = "myController">
<a title="Edit in new window" ng-click="openWindow() >Test edit</a>
</div>
In angular js you can do something like,
$scope.newWindow = function(value) {
$window.open("http://your_url/routing_path/?key=value", "");
};
where routing_path will be ur routing url and value is the part where you actually send your value.
In your script you can have like
var myApp = angular.module('myApp', []);
myApp.run(function($rootScope, $location, $http, $timeout) {
$rootScope.$on('$routeChangeStart', function(event, next) {
if (next.originalPath === 'your_routing_url') {
var value = next.params.key;
}
});
});
Also you can use Web Storage object like,
var myApp = angular.module('myApp', []);
myApp.factory('anyService', function($http, $location) {
return {
set: function(key,value) {
return localStorage.setItem(key,value);
},
get: function(key) {
return localStorage.getItem(key);
},
destroy: function(key) {
return localStorage.removeItem(key);
}
};
});
inject the service any where and get the data.

AngularJS - How do I modify variables in $rootScope?

I've got a potentially really dumb question, but how do I modify variables up in $rootScope in Angular? I've got a slide-in sidebar that I want to change the content on whenever someone clicks on a thumbnail, and I figured the easiest way to handle where the data in the sidebar comes from/the sidebar visibility would either be in global values, or in $rootScope. I'm trying to keep everything as simple as possible, but I just don't know how to handle modifying global variables.
My angular code surrounding this is:
app.run(function($rootScope) {
$rootScope.currentUrl = { value: 'visual/design/1/' };
$rootScope.detail_visible = { value: true };
});
app.controller('navController', ['$scope', '$rootScope',
function ($scope, $rootScope) {
$scope.isDetail = $rootScope.detail_visible.value;
$scope.url = $rootScope.currentUrl.value;
$scope.hide = function($rootScope) {
$rootScope.detail_visible.value = false;
};
}]);
and the connecting HTML is
<div id="detail_box" ng-class="{d_show: isDetail, d_hide: !isDetail}">
<div ng-include="url + 'detail.html'"></div>
</div>
In essence, I'm trying to make it so that when you click on a thumbnail, it changes the currentUrl value from 'visual/design/1/' to whatever they've clicked on (like, 'music/solo/2' or whatever) then changes the value of detail_visible to false, so that the classes on my sidebar switch and I get a nice little slide-in, with fresh content loaded via ng-include which I kind of love a thousand times more than I thought I would. I've been banging my head against this for about three hours now, breaking everything else on this app whenever I get the chance. What am I screwing up here? Alternatively, is there a better way of doing this?
My reason for using global variables is that I have multiple thumbnails in multiple controllers, and I want each one to be able to dynamically change the URL in my ng-include.
For your question, you change the $rootScope variable simple by referencing it with
$rootScope.detail_visible.value = newValue;
but you dont need to inject $rootScope to your function:
$scope.hide = function() { //without $rootScope
$rootScope.detail_visible.value = false;
};
But, I would suggest you to implement a service and not to pollute the rootscope for such task.
https://docs.angularjs.org/guide/services
Object properties of scopes are inherited -- in your controller, you should be able to modify $scope.detail_visible.value and see it affect the $rootScope. You still have to initialize it on the $rootScope in .run() though.
app.run(function($rootScope) {
$rootScope.currentUrl = { value: 'visual/design/1/' };
$rootScope.detail_visible = { value: true };
});
app.controller('navController', ['$scope', function ($scope, $rootScope) {
$scope.hide = function() { // don't need to pass an argument
$scope.detail_visible.value = false;
};
}]);
view:
<div id="detail_box" ng-class="{d_show: currentUrl.value, d_hide: !currentUrl.value}">
<div ng-include="currentUrl.value + 'detail.html'"></div>
</div>

What is a correct way to bind document level key events in AngularJS specific only to a certain route/controller?

I have a single page app that opens a gallery. I want to bind document level keyup event (for keyboard gallery controlls) only when the gallery is open, ie. when route matches
.when('/reference/:galleryId/:imageId/', { templateUrl: '/partials/gallery.htm', controller: 'galleryController', controllerAs: 'reference' })
and I want to unbind it when I leave this route.
One thing that might be a problem is, I block reloading the view between images within the same gallery with this:
.run(['$route', '$rootScope', '$location', function ($route, $rootScope, $location) {
var original = $location.path;
$location.path = function (path, reload) {
if (reload === false) {
var lastRoute = $route.current;
var un = $rootScope.$on('$locationChangeSuccess', function () {
$route.current = lastRoute;
un();
});
}
return original.apply($location, [path]);
};
}])
Demo (Click on "Fotografie" to open the gallery)
http://tr.tomasreichmann.cz/
Angular wiz to the rescue?
Thank you for your time and effort
You could bind a keyup event to $document in your controller's constructor and then unbind the event when the controller's $scope is destroyed. For example:
.controller('galleryController', function ($scope, $document) {
var galleryCtrl = this;
function keyupHandler(keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
$scope.$apply(); // remove this line if not need
}
$document.on('keyup', keyupHandler);
$scope.$on('$destroy', function () {
$document.off('keyup', keyupHandler);
});
...
});
There will be nothing left behind when the view become inactive.
If you feel it isn't right to do this in the controller, you could move this into a custom directive and place it in a template of the view.
Finally I stuck with
<html xmlns="http://www.w3.org/1999/xhtml"
xmlns:og="http://ogp.me/ns#"
xmlns:fb="http://www.facebook.com/2008/fbml"
lang="cz"
ng-app="profileApp"
ng-keyup="$broadcast('my:keyup', $event)" >
Not sure if this is good practice, but it registers within my controller
$scope.$on('my:keyup', function(event, keyEvent) {
console.log('keyup', keyEvent);
galleryCtrl.keyUp(keyEvent);
});
And doesn't do anything when the current route is not active
I found this answer here: https://groups.google.com/forum/#!searchin/angular/document$20level$20events/angular/vXqVOKcwA7M/RK29o3oT9GAJ
There is another way to bind it globally which wasn't my goal, the original code in question did what I needed.

Angularjs: controller init function avoid init to be called

I have the following controller code:
applicationControllers.controller('PostsController', ['$scope', '$http', function
($scope, $http) {
var page = 1;
$scope.init = function() {
this.loadPage(page);
}
$scope.nextPage = function() {
page++;
this.loadPage(page);
}
$scope.previousPage = function() {
page--;
if (page <= 0) { page = 1 }
this.loadPage(page);
}
$scope.filterByProvince = function(provinceName) {
console.log(provinceName);
}
$scope.loadPage = function(page) {
$http.get('/posts.json?page=' + page).success(function(data) {
$scope.posts = data;
});
}
$scope.init();
}]);
The problem is when using a ng-click directive to filterByProvince('test'), it seems the init function are also called. I want to avoid this behaviour.
Any help?
In Angular, when you want to have an <a> element display like an <a> element, but used differently (e.g. as a button (by attaching some ngClick directive)), you should use an empty href:
Do something
The problem with using href="#" is that it is eihter:
Recognized by ngRoute (if it is used) as a "link" to the home route ('/'), so even if you are in that route it gets reloaded causing the effects of the ngClick callback to be voided or
Recognized by the browser as link with an anchor tag to the same page, so clicking it causes the page to reload (again voiding any effect of the ngClick callback).
I am not sure how ui-router handles this, but I am not a big fan of ui-router - maybe someone with more experience can give some feedback.

"Computed Properties" in AngularJS

I recently chose AngularJS over ember.js for a project I am working on, and have been very pleased with it so far. One nice thing about ember is its built in support for "computed properties" with automatic data binding. I have been able to accomplish something similar in Angular with the code below, but am not sure if it is the best way to do so.
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// "Computed Property"
$scope.$watch(navigation.getCurrentPageNumber, function(newVal, oldVal, scope) {
scope.currentPageNumber = newVal;
});
$scope.totalPages = navigation.getTotalPages();
}]);
// 'navigation' service
angular.module('mathSkills.services', [])
.factory('navigation', function() {
var currentPage = 0,
pages = [];
return {
getCurrentPageNumber: function() {
return currentPage + 1;
},
getTotalPages: function() {
return pages.length;
}
};
});
// HTML template
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber}} of {{totalPages}}
</div>
I would like for the UI to update whenever the currentPage of the navigation service changes, which the above code accomplishes.
Is this the best way to solve this problem in AngularJS? Are there (significant) performance implications for using $watch() like this? Would something like this be better accomplished using custom events and $emit() or $broadcast()?
While your self-answer works, it doesn't actually implement computed properties. You simply solved the problem by calling a function in your binding to force the binding to be greedy. I'm not 100% sure it'd work in all cases, and the greediness might have unwanted performance characteristics in some situations.
I worked up a solution for a computed properties w/dependencies similar to what EmberJS has:
function ngCreateComputedProperty($scope, computedPropertyName, dependentProperties, f) {
function assignF($scope) {
var computedVal = f($scope);
$scope[computedPropertyName] = computedVal;
};
$scope.$watchCollection(dependentProperties, function(newVal, oldVal, $scope) {
assignF($scope);
});
assignF($scope);
};
// in some controller...
ngCreateComputedProperty($scope, 'aSquared', 'a', function($scope) { return $scope.a * $scope.a } );
ngCreateComputedProperty($scope, 'aPlusB', '[a,b]', function($scope) { return $scope.a + $scope.b } );
See it live: http://jsfiddle.net/apinstein/2kR2c/3/
It's worth noting that $scope.$watchCollection is efficient -- I verified that "assignF()" is called only once even if multiple dependencies are changed simultaneously (same $apply cycle).
"
I think I found the answer. This example can be dramatically simplified to:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', ['navigation', '$scope', function (navigation, $scope) {
// Property is now just a reference to the service's function.
$scope.currentPageNumber = navigation.getCurrentPageNumber;
$scope.totalPages = navigation.getTotalPages();
}]);
// HTML template
// Notice the first binding is to the result of a function call.
<div id=problemPager ng-controller=nav>
Problem {{currentPageNumber()}} of {{totalPages}}
</div>
Note that with ECMAScript 5 you can now also do something like this:
// Controller
angular.module('mathSkills.controller', [])
.controller('nav', function(navigation, $scope) {
$scope.totalPages = navigation.getTotalPages();
Object.defineProperty($scope, 'currentPageNumber', {
get: function() {
return navigation.getCurrentPageNumber();
}
});
]);
//HTML
<div ng-controller="nav">Problem {{currentPageNumber}} of {{totalPages}}</div>

Resources