Controller function called twice in angular - angularjs

I'm currently getting to grips with angular and I'm a complete new guy to it, I've put together a jsfiddle for the issue.
But in short I'm not sure why my function attached to the angular scope is called twice.
Script
var registrationModule = angular.module("registrationModule", []);
registrationModule.controller("NavigationController", function ($scope) {
$scope.menuItems = [{ text: 'Home'}];
alert("hi");
$scope.getClass = function (path) {
alert("getClass");
return "frank";
};
});
HTML
<body ng-app="registrationModule">
<div class="collapse navbar-collapse navbar-responsive-collapse">
<ul class="nav navbar-nav navbar-right" ng-controller="NavigationController">
hi {{ getClass(); }} butcher
</ul>
</div>
</body>
http://jsfiddle.net/6F55K/1/
Any tips?

The Angular digest loop is built to work this way. Any expressions in your view will be executed at least once.
When the view is compiled it will call your getClass() function to get a value, then it will run the digest loop again to see if anything has changed since its previous call. This is how it knows that your view is up to date.

Noel's answer didn't work for me. How about changing the function so it sets a variable?
registrationModule.controller("NavigationController", function ($scope) {
$scope.menuItems = [{ text: 'Home'}];
alert("hi");
$scope.setClass = function (path) {
alert("setClass");
$scope.class = "frank";
};
});
Check out this fiddle: http://jsfiddle.net/6F55K/3/

Related

AngularJS - Callback after ng-repeat update

I got some trouble understanding how I make a callback after I've updated an ng-repeat. I basically want to be able to make a callback function after my updates to my ng-repeat has been finished. Currently have this:
var app = angular.module('myApp', []);
app.directive('onLastRepeat', function() {
return function(scope, element, attrs) {
if (scope.$first)
console.log("ng-repeat starting - Index: " + scope.$index)
if (scope.$last) setTimeout(function(){
console.log("ng-rpeat finished - Index: " + scope.$index);
}, 1);
};
});
app.controller('MyController', function($scope) {
$scope.data = [1,2,3,4,5,6,7,8,9,10,12,12,13,14,15,16,17,18,19,20];
$scope.buttonClicked = function() {
console.log('Btn clicked');
$scope.randomItems = getRandomItems(this.data.length);
};
});
HTML
<div ng-app="myApp">
<div ng-controller="MyController">
<button ng-click="buttonClicked()">Highlight random</button>
<ul class="item" >
<li ng-repeat="item in data" ng-class="{highlight: randomItems.indexOf($index) > -1}" on-last-repeat>{{ item }} </li>
</ul>
</div>
</div>
Link to fiddle: https://jsfiddle.net/hbhodgm3/
So how the "app" works is that it lists the content of the data-array then when you click the "highlight"-button it randomly highlights 2 in the list. So my problem is that I want to have a callback function for when the highlighting/DOM-render is done. I found a way to do this for the initial ng-repeat with $scope.first and $scope.last to check when ng-repeat is done, but doesn't seem to work with the highlighting.
Hope I managed to explain the problem,
Thanks in advance.
See $q and Promises for a better understanding of how to work with the asynchronous nature of angular.
Presuming getRandomItems(this.data.length); is an API call that could take seconds to perform:
asyncItems(this.data.length).then(function(randoms){
$scope.randomItems = randoms;
//handle post rendering callback
});
function asyncItems(length) {
var deferred = $q.defer();
var items = getRandomItems(length);
if (items){
deferred.resolve(items);
}
else {
//no items :(
deferred.reject([]);
}
return deferred.promise;
}

How to reflesh $scope in Angular in AJAX reponse?

I have AJAX response inside that is deleted object:
request.success(function (data) {
delete $scope.notifications.system[key];
$scope.$apply();
});
I have HTML code with block, that would be appear by condition:
<span ng-show="notifications.system.length == 0" ng-cloak>
DELETED
</span>
So, I tried to use $scope.$apply(); in response at once after removing object. But I have got error:
Error: [$rootScope:inprog] http://errors.angularjs.org/1.3.13/$rootScope/inprog?p0=%24digest
How I can reload template when notifications.system.length is equal zero?
When you use delete on arrays it doesn't change the length of the array instead it replaces the element in the array with undefined. So your ng-show never changes because the length of the array isn't changing. Use splice instead and the array will shorten and your $scope should update at you expect.
$scope.notifications.system.splice($scope.notifications.system.indexOf(key), 1);
you shouldn't need $scope.$apply() for something like this.
If you choose to use the $scope.$apply() you should wrap everything in a $timeout and call it like this.
request.success(function(resp){
$timeout(function(){
$scope.$apply(function(){
//do stuff here to the scope.
});
});
});
Passing in a function reference to $apply will cause it to execute that function then $digest. Seems a bit strange I know, but the reason for this is that AngularJS typically calls $digest in response to user interaction, not necessarily to events like $http.success.
You could also do the managing of your errors differently.
Instead of adding directly to an object you could add the error objects to an array.
Deleting can then be done with the following code:
$scope.removeError = function (errorName) {
angular.forEach($scope.notifications, function (error, index) {
if (error.hasOwnProperty(errorName)) $scope.notifications.pop(index);
});
};
Have a look at the demo below and here at jsfiddle.
angular.module('myApp', [])
.controller('mainController', MainController);
function MainController($http, $scope, $timeout) {
$scope.notifications = [{
errorImg: 'failed to load image',
}//,
/*{ // other errors
//errorJS: 'failed to load js'
}*/];
$scope.removeError = function (errorName) {
angular.forEach($scope.notifications, function (error, index) {
//console.log(index, error.hasOwnProperty(errorName), $scope.notifications);
if (error.hasOwnProperty(errorName)) $scope.notifications.pop(index);
//console.log(index, error.hasOwnProperty(errorName), $scope.notifications);
});
};
$scope.clear = function () {
$http.jsonp('http://www.mocky.io/v2/556f7ba53db19a8f05f1e555?callback=JSON_CALLBACK')
.success(function (response) {
//dummy request
//console.log(response, $scope.notifications);
//delete $scope.notifications['errorImg'];
$scope.removeError('errorImg');
}) //.bind(this))
}
}
MainController.$inject = ['$http', '$scope', '$timeout'];
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp" ng-controller='mainController as main'> <pre>{{notifications |json}}</pre>
<!--<button ng-click="main.show()">Show data</button>
<ul>
<li ng-repeat="info in main.data">{{info.test}}</li>
</ul>-->
<button ng-click="clear()">clear error</button>
<ul>
<li ng-repeat="i in dummy">{{i}}</li>
</ul>
<div ng-show="notifications.length == 0">deleted</div>
</div>

Modifying the $scope in a callback within a controller function

Here is my Angular.js controller function code:
myControllers.controller("TreeDataController", ["$scope", function ($scope) {
$scope.greeting = "TreeDataController";
treeDataSource.getData("123", function (rootNode) {
// this is the callback when the AJAX operation succeeds, rootNode is a JSON object which contains hierarchical data.
$scope.tree = rootNode;
console.log("Got rootNode.");
$scope.foobar = "baz";
}, function (e) { alert(e); });
}]);
Here is my View:
<h2>Queries</h2>
{{greeting}}
{{foobar}}
<script type="text/ng-template" id="treeItemRenderer.html">
{{node.name}}
<ul>
<li ng-repeat="node in node.value" ng-include="''treeItemRenderer.html''"></li>
</ul>
</script>
<ul>
<li ng-repeat="node in tree" ng-include="''treeItemRenderer.html''">
{{node.name}}
</li>
</ul>
When I run this page in Chrome, the page displays "TreeDataController" in the {{greeting}} placeholder, and the console reports it got the data (see my console.log call), however the tree data is never displayed. I thought it was a problem with my recursive-template so I added $scope.foobar = "baz"; and {{foobar}}, however that isn't populated either.
This is a common error you can get when dealing with Angular.
Because you're updating the $scope outside of a scope apply, you aren't actually causing Angular to do any template rerendering.
This is fairly simple to fix, wrap any $scope calls within $scope.$apply, see the following:
treeDataSource.getData("123", function (rootNode) {
$scope.$apply(function () {
$scope.tree = rootNode;
console.log("Got rootNode.");
$scope.foobar = "baz";
});
}, function (e) { alert(e); });

angular scope variable has no data

I'm pretty new to angular js but it seems like my simple code should work. Here is the html:
<body ng-app="MyHomepage">
<div ng-controller="RedditLoad">
{{a}}
<ul>
<li ng-repeat="article in a">
{{article.data.title}}
</li....
and here is my angualr_app.js:
var App = angular.module('MyHomepage', [])
function RedditLoad($scope){
$.getJSON("http://www.reddit.com/.json?jsonp=?", function(data) {
var data_array = [];
$.each(data.data.children, function(i,item){
data_array.push(item);
});
console.log(data_array);
$scope.a = data_array;
});
}
What am I doing wrong? console.log(data_array); is showing the correct values but the data wont seem to get passed to the template.
The getJSON callback isn't executed in the angular context so angular doesn't know about your changes and won't refresh the bindings. When code is called from an external source (like a jQuery event ), you have to encapsulate your scope changes in an $apply call:
$scope.$apply(function{
$scope.a = data_array;
}

AngularJS - Using a Service as a Model, ng-repeat not updating

I'm creating an ajax search page which will consist of a search input box, series of filter drop-downs and then a UL where the results are displayed.
As the filters part of the search will be in a separate place on the page, I thought it would be a good idea to create a Service which deals with coordinating the inputs and the ajax requests to a search server-side. This can then be called by a couple of separate Controllers (one for searchbox and results, and one for filters).
The main thing I'm struggling with is getting results to refresh when the ajax is called. If I put the ajax directly in the SearchCtrl Controller, it works fine, but when I move the ajax out to a Service it stops updating the results when the find method on the Service is called.
I'm sure it's something simple I've missed, but I can't seem to see it.
Markup:
<div ng-app="jobs">
<div data-ng-controller="SearchCtrl">
<div class="search">
<h2>Search</h2>
<div class="box"><input type="text" id="search" maxlength="75" data-ng-model="search_term" data-ng-change="doSearch()" placeholder="Type to start your search..." /></div>
</div>
<div class="search-summary">
<p><span class="field">You searched for:</span> {{ search_term }}</p>
</div>
<div class="results">
<ul>
<li data-ng-repeat="item in searchService.results">
<h3>{{ item.title }}</h3>
</li>
</ul>
</div>
</div>
</div>
AngularJS:
var app = angular.module('jobs', []);
app.factory('searchService', function($http) {
var results = [];
function find(term) {
$http.get('/json/search').success(function(data) {
results = data.results;
});
}
//public API
return {
results: results,
find: find
};
});
app.controller("SearchCtrl", ['$scope', '$http', 'searchService', function($scope, $http, searchService) {
$scope.search_term = '';
$scope.searchService = searchService;
$scope.doSearch = function(){
$scope.searchService.find($scope.search_term);
};
$scope.searchService.find();
}]);
Here is a rough JSFiddle, I've commented out the ajax and I'm just updating the results variable manually as an example. For brevity I've not included the filter drop-downs.
http://jsfiddle.net/XTQSu/1/
I'm very new to AngularJS, so if I'm going about it in totally the wrong way, please tell me so :)
In your HTML, you need to reference a property defined on your controller's $scope. One way to do that is to bind $scope.searchService.results to searchService.results once in your controller:
$scope.searchService.results = searchService.results;
Now this line will work:
<li data-ng-repeat="item in searchService.results">
In your service, use angular.copy() rather than assigning a new array reference to results, otherwise your controller's $scope will lose its data-binding:
var new_results = [{ 'title': 'title 3' },
{ 'title': 'title 4' }];
angular.copy(new_results, results);
Fiddle. In the fiddle, I commented out the initial call to find(), so you can see an update happen when you type something into the search box.
The problem is that you're never updating your results within your scope. There are many approaches to do this, but based on your current code, you could first modify your find function to return the results:
function find(term) {
$http.get('/json/search').success(function(data) {
var results = data.results;
});
//also notice that you're not using the variable 'term'
//to perform a query in your webservice
return results;
}
You're using a module pattern in your 'public API' so your searchService returns the find function and an array of results, but you'd want to make the function find to be the responsible for actually returning the results.
Setting that aside, whenever you call doSearch() in your scope, you'd want to update the current results for those returned by your searchService
$scope.doSearch = function(){
$scope.searchService.results = searchService.find($scope.search_term);
};
I updated your jsfiddle with my ideas, is not functional but i added some commments and logs to help you debug this issue. http://jsfiddle.net/XTQSu/3/

Resources