How to reflesh $scope in Angular in AJAX reponse? - angularjs

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>

Related

Angular $scope not getting variables

I have a simple html file that make make search on Algolia and returns result. I can console the result but can not access $scope.users from view. How can I grab that $scope.users in view.
here is my app.js file
var myApp = angular.module('myApp', []);
myApp.controller('usersController', function($scope) {
$scope.users = [];
var client = algoliasearch('my_app_id', 'my_app_key');
var index = client.initIndex('getstarted_actors');
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
console.log(content.hits);
});
});
Here is my html view file
<div class="results" ng-controller="usersController">
<div ng-repeat="user in users">
<h3>{{ user.name }}</h3>
</div>
</div>
note: ng-app="myApp" attribute given in html tag.
It's most likely because your index.search call isn't triggering an Angular $digest cycle - manually trigger one with either $apply or a $timeout
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
$scope.$apply();
});
The $apply() could throw $digest already in progress errors - another way with a $timeout
myApp.controller('usersController', function($scope, $timeout) {
index.search('john', function searchDone(err, content) {
$timeout(function() {
$scope.users.push(content.hits);
});
});
});
try calling $scope.$apply() to update your bindings
index.search('john', function searchDone(err, content) {
$scope.users.push(content.hits);
console.log(content.hits);
$scope.$apply();
});
algoliasearch doesn't look like it's an injected service and therefore not native to the angular framework. Try calling $scope.$apply().

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;
}

Show "No items found" with ng-show only after loading completes

Let's say, I have two divs like this:
<div ng-show="!items.length>
<p>No items found</p>
</div>
<div ng-show="items.length>
<li ng-repeat="item in items">
...
...
</div>
Now $scope.items is populated using an AJAX call. Now whenever I load this page, it initially shows me that No items found. But after a second or so (when the AJAX call gets completed), it displays me the other <div> and hides the No items found. I was wondering if there is a better alternative technique for solving this problem ?
UPDATE: Controller looks like this (simplified for the question):
myApp.controller('Controller', function($scope, $http) {
$http.get(fetchURL).
success(function(data, status, headers, config) {
$scope.items =data
});
});
dont create $scope.items before the ajax is complete, so create $scope.items only after ajax request is complete and data is ready.
for ex:
$http.get(fetchURL).success(function(data) {
$scope.items = data; // create $scope.items here, and don't create before this.
});
then
<div ng-show="items && (items.length ==0)">
<p>No items found</p>
</div>
<div ng-if="items">
<div ng-show="!items.length">No items found.</div>
...
</div>
This will only render the content after the items are actually created.
You can use the resolve parameter of your routeProvider#when. See in the docs that this parameter is:
An optional map of dependencies which should be injected into the controller. If any of these dependencies are promises, the router will wait for them all to be resolved or one to be rejected before the controller is instantiated
So, in your case, you can do something like this:
$routeProvider.when('/url', {
templateUrl: 'partials/yourhtml.html',
controller: 'yourController',
resolve: {
'items' : ['$http', function($http) {
return $http.get("items");
}]
}
});
And in controller you inject the 'items' as a parameter:
myApp.controller('Controller', function($scope, items) {
$scope.items = items.data;
}]);

AngularJS won't update view

I'm just new to angularJS and have my first big problem.
First I wanted to create a list and update it, but it doesn't work.
// index.html
<form ng-controller="ListCtrl">
<div class="form-group">
<input name="listname" ng-model="listname">
<input type="button" ng-click="set()" value="add new list">
</div>
</form>
<div ng-controller="ListCtrl">
<ul>
<li ng-repeat="list in lists">
{{list.id}} - {{list.listname}}
</li>
</ul>
{{output}}
</div>
The fun part is the resetForm(), there I reset the form but also will try to update the $scope.output. But that output never changes, no matter what I try to change.
// ListCtrl
var ListApp = angular.module('ListApp', []);
ListApp.controller('ListCtrl', function ($scope) {
// add new records to database
$scope.set = function() {
$scope.createTableIfNotExists();
$scope.insertSql = 'INSERT INTO Lists (listname) VALUES (?)';
if ($scope.listname) {
$scope.db.transaction(
function (transaction) {
transaction.executeSql($scope.insertSql, [$scope.listname], resetForm);
}
);
}
};
function resetForm() {
// clear the input field
$scope.listname = "";
$scope.listForm.$setPristine();
$scope.output = 'Hello World';
$scope.$apply();
}
Edit:
When I try this with $apply the console shows me an error:
function resetForm() {
$scope.$apply(function () {
$scope.listname = "";
$scope.listForm.$setPristine();
$scope.output = 'Hello World';
});
}
And the error is:
Uncaught TypeError: undefined is not a function
The error points direct to the start of "$scope.$apply(function..."
Edit 2:
When I add a new button in index.html and call a function with ng-click and in this function I just say
$scope.output = 'Hello World!';
Then my view updates.
It will only not update when I use a callback to change the scope. Don't understand that. I thought everything is connected in angularJS, especially when I'm in the same controller.
Edit 3:
Here is the plunker
Uncaught TypeError: undefined is not a function
That's because you called $scope.apply instead of $scope.$apply.
Try calling it with a callback function:
$scope.$apply(function () {
$scope.listname = "";
$scope.listForm.$setPristine();
$scope.output = 'Hello World';
});

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); });

Resources