I'm looking for a pure angularJS way to call a controller method once a particular dom element is rendered. I'm implementing the scenario of a back button tap, so I need to scroll to a particular element once it is rendered. I'm using http://mobileangularui.com/docs/#scrollable.
Update: how my controller looks like:
$scope.item_ready=function(){
return document.getElementById($scope.item_dom_id).length;
};
$scope.$watch('item_ready', function(new_value, old_value, scope){
//run once on page load, and angular.element() is empty as the element is not yet rendered
});
Thanks
One hack that you could do and I emphasize hack here but sometimes it's just what you need is watch the DOM for changes and execute a function when the DOM hasn't changed for 500ms which is accepted as a fair value to say that the DOM has loaded. A code for this would look like the following:
// HACK: run this when the dom hasn't changed for 500ms logic
var broadcast = function () {};
if (document.addEventListener) {
document.addEventListener("DOMSubtreeModified", function (e) {
//If less than 500 milliseconds have passed, the previous broadcast will be cleared.
clearTimeout(broadcast)
broadcast = $window.setTimeout(function () {
//This will only fire after 500 ms have passed with no changes
// run your code here
}, 10)
});
}
Read this post Calling a function when ng-repeat has finished
But don't look at the accepted answer, use the 3rd answer down by #Josep by using a filter to iterate through all your repeat items and call the function once the $last property returns true.
However instead of using $emit, run your function...This way you don't have to rely on $watch. Have used it and works like a charm...
Related
In a particular scenario, I need to call the the github api to retrieve a specific user's info. Then issue a second call to retrieve the user's repositories:
search(login: string): void {
this.user = undefined;
// first call
this.githubApi.getUser(login)
.then(user => {
this.user = user;
// second call
this.githubApi.getRepos(user.repos_url)
.then(reposResponse => {
this.repos = reposResponse.repos;
// I don't like to call this.$scope.$apply() !!;
});
});
}
The first call gets executed and the bound elements to this.user gets updated with no problem in the view.
The second call gets executed and the result is returned successfully and this.repos is set correctly. But, the bound elements on the view are not updated.
If I call this.$scope.$apply() in the very last line of the second callback, it makes the view update work but I guess this is not correct approach.
Any solution?
Well, if you are not willing to use $scope.apply();, try updating your getRepos service response code with:
setTimeout(
() => {
this.repos = reposResponse.repos;
}, 0
)
First you need to know , why Angular-Js is not updating the view.
You have used $scope.$apply(), so I'm assuming you already know , how it works and why we use it. Now , to the problem!
Sometimes when you make a callback - nested callback in particular - Angular does not update the view. Sometimes angular thinks that it does not need to update the view because of callbacks. And the watchers do not take action when the value changes of the variable that they are watching.
Then you use $scope.$apply() to run the digest cycle again (assuming you already know the digest cycle if you don't then let me know). And it makes the watchers to update the view.In your case, digest cycle is not running, that is why angular is not updating the view. If your digest cycle was running , angular would have given you error. So, it will tell angular to run digest cycle again because two-way binding is not working properly.
I don't think there is another way. But if there is a way, I would love to know that way. Also its not a bad approach. It was made for these kind of problems.
This one has me confounded. I have looked far and wide and am out of ideas. In my searching, I discovered that one of the common reasons for multiple function calls on load is if you have a controller defined in routes and via the ngController directive. Checked this - I do not use ngController. I also checked my routes - seem in order. There are no $watch functions that could be causing $digest issues. This function is called one time, at the bottom of the function, and the console.log is logged out 5x...EVERY TIME. I have even set a $timer function and it still calls it 5x. Have tried creating a variable to only run if it hasn't been run before but it seems like it's all happening with the getQuotas() method. Any help would be greatly appreciated!
function getQuotas ()
{
console.log('getQuotas'); //This logs out 5x
UserService.getQuotas()
.then(function(res)
{
if (res.data.success)
{
quotaData = res.data.data;
getQuotas_success();
return true;
}
else
{
getQuotas_failure();
return false;
}
}, function (err)
{
getQuotas_failure();
return false;
});
}
getQuotas(); //Function is called here.
Solved it! I'm hopeful this will help others. There was a custom attribute directive on each of 4 input fields on this page. That particular directive was using the same controller as the page itself. So the controller was getting loaded a total of 5 times. Fortunately for me, this directive is now deprecated but I would probably redo it by either creating a directive-level controller and using the 'require' attribute in the directive's return object, pointing to the page-level controller, OR just have the data that needs to get passed between the page-level controller and the directive go through a service.
I'm using angular chosen with angularstrap and i'm having problems with the initial value of the selector to be selected. The way i got it to work is i set a Timeout on the model attached to the selector to wait for the dom and then set the model value. So my guess is that chosen needs to wait for the dom to be created before it can initialize the selected option.
$scope.showModal = function() {
myModal.$promise.then(myModal.show);
// hack to make chosen load
$timeout(function () {
myModal.$scope.SelectedColor = "green";
}, 500 );
};
in my opinion this timeout solution is not a good one and i would like to find a better way to set the model after the dom has been created.
This is because chosen directive is calling trigger("chosen:updated") before the DOM is actually loaded. A fix would be adding $timeout() to the $watchCollection trigger.
This has been discussed and looks like the solution is here in the answer from kirliam.
Someone should issue a pull request for this issue.
edit: I issued a pull request for a fix regarding this issue. Hope it gets merged in.
I'm using Angular JS and I've implemented my form with a reset function.
setPrimarySelectionEditEntry is called when I start editing the item on the form. primarySelectionEntry keeps the object that is bound to controls
savePrimarySelectionEdit is called when user hits Save. What I do is I replace item in primarySelection array, as I use this array to display a list
cancelPrimarySelectionEdit does nothing except setting the form to pristine state. Underlying array remains intact
$scope.cancelPrimarySelectionEdit = function () {
$scope.primarySelectionForm.$setPristine();
};
$scope.savePrimarySelectionEdit = function () {
$scope.topicSelection.primarySelection[$scope.formEntryIndex] = $scope.primarySelectionEntry;
$scope.cancelPrimarySelectionEdit();
};
$scope.setPrimarySelectionEditEntry = function (entry) {
$scope.formEntryIndex = $scope.topicSelection.primarySelection.indexOf(entry); ;
$scope.primarySelectionEntry = angular.copy(entry);
};
It works: when I hit save item in the list reflects new values. When I hit cancel, then nothing happens. But I wonder if this replacing array item is safe in the context of angular?
As long as you change the model within Angular's execution context it'll be safe to replace some elements or even the whole array. It won't break neither the data binding nor the event loop.
You only have to worry about changes done outside Angular's loop. For example, a DOM event listener in a directive. Let's say you have the following directive:
angular.module('myApp').directive('resetForm', function(){
return {
link: function($scope, $iElement){
//Add a listener to the element
$iElement.on('click', function(){
//Carefull: changes in scope in this context won't trigger an angular event loop.
$scope.cancelPrimarySelectionEdit();
//So, call $digest to process all the watchers
$scope.$digest();
return;
});
return;
}
}
});
Here, the $scope.cancelPrimarySelectionEdit() function can be called at any time, so a call to $scope.$apply() or $scope.$digest() should be added to tell Angular that something in the model has changed and needs to execute all the watchers from the current scope and its children.
So today I just came across the 'live()' function that binds any future and past elements to the whatever event you choose, such as 'onclick'.
Right now I'm having to set up buttons like the following each time I load a new button via ajax ...
$('a.btn.plus').button({icons:{primary:'ui-icon-plusthick'}});
$('a.btn.pencil').button({icons:{primary:'ui-icon ui-icon-pencil'}});
$('a.btn.bigx').button({icons:{primary:'ui-icon ui-icon-closethick'}});
So, instead of calling these lines each time I use ajax to add a new button, is there a similar way to tell JQuery to setup my buttons ANYTIME I add new ones?
Thanks for any help!
Mmh not really. But there is the function .ajaxSuccess() which is triggered whenever an Ajax call is successful. So you could do:
$('body').ajaxSuccess(function() {
$('a.btn.plus').button({icons:{primary:'ui-icon-plusthick'}});
$('a.btn.pencil').button({icons:{primary:'ui-icon ui-icon-pencil'}});
$('a.btn.bigx').button({icons:{primary:'ui-icon ui-icon-closethick'}});
});
But this will run on any links with the classes, not only on the new ones. But if you append them on a time (i.e. not multiple a.btn.plus at once) you might be able to use the :last selector (a.btn.plus:last).
You can also create a function and just that from your callback functions:
function links() {
$('a.btn.plus').button({icons:{primary:'ui-icon-plusthick'}});
$('a.btn.pencil').button({icons:{primary:'ui-icon ui-icon-pencil'}});
$('a.btn.bigx').button({icons:{primary:'ui-icon ui-icon-closethick'}});
}
and in the Ajax call:
$.ajax({
//...
success: function(msg){
links();
}
});
This way you can pass the parent element to the function in order to find the link only inside this element (so the code would only work on the new links).
A last option would be generate a custom event but in the end this would be similar to just doing a function call in your case so you gain not much.
You can use delegate in your success function too
$("body").delegate("a.btn", "hover", function(){
$(this).toggleClass("hover");
});
There is a Jquery Plugin called livequery which covers your requirements.
I like to think of this plugin as Jquery .live() but without the need for an event ('click') etc.
You can find more info here//
Jquery - Live Query Plugin