In Angular, what is the best practice for triggering code after multiple models have been populated by resource services. Nest $scope.$watch?
Right now, I'm cheating and checking off values in an array, which doesn't feel very "angular."
$scope.loaded = [];
$scope.modelA = aResource.query({}, function() {$scope.loaded.push('a')});
$scope.modelB = bResource.query({}, function() {$scope.loaded.push('b')});
$scope.$watch(loaded.length, function(newValue) {
if ($scope.loaded.indexOf(modelA) != -1 && $scope.loaded.indexOf(modelB) != -1) {
console.log('done!');
}
});
https://groups.google.com/forum/?fromgroups=#!topic/angular/TizlifUL7FU
If you are using Angular routing, this is normally accomplished with the resolve parameter of the when() method. See also Delaying AngularJS route change until model loaded to prevent flicker
If you don't want to delay your route change until the data is loaded, you can set up your own promises using $q. Use $q.all() to wait for all promises to resolve. See https://stackoverflow.com/a/15117739/215945 and https://stackoverflow.com/a/14545803/215945
Related
I'm having a controller using StompJS to subscribe to a url (back-end is Spring Java) that returns an alternating string "list" and "box" every 5 seconds. I want to update my UI element when StompJS receives some data, but I couldn't get the UI element to update. I've test the same logic with a $timeout and the UI is getting updated so it must has something to do with the way callback function works. Can anyone see what is the reason UI is not updating?
I have these simple UI elements:
<input ng-model="ctrl.uniqueId"/>
<input ng-model="test"/>
ctrl.uniqueId is to verify whether the actual controller instance is being updated. For some reason, only 1 controller is making 5 different subscribes every time. If someone can help with that, it'd be great too but I doubt you can get much info unless you see all my codes setup.
Anyway, in my controller (tried self.test and it didn't work so I tried with $scope.test to see if it makes a difference):
self.uniqueId = window.performance.now();
$scope.test = 'list';
// the UI will be updated to dummy after 3 seconds.
$timeout(function() {
$scope.test="dummy";
}, 3000);
// the UI will not update.
var callBackFn = function(progress) {
$scope.test = progress;
console.log(self.uniqueId + ": " + $scope.test);
};
// the server returns alternating data (list and box) every 5 seconds
MyService.subscribeForUpdate('/topic/progressResults', callBackFn);
This is my service's code for StompJS if that matters:
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
callBackFn(result.body);
return result.body;
}
);
});
};
This is console.log results:
1831.255000026431: list
1831.255000026431: box
Extra: is it possible to get the return data without callback function similar to Promise?
Be sure to use $apply:
app.service("myService", function($rootScope) {
var self = this;
self.subscribeForUpdate = function(channelUrl, callBackFn) {
self.socket.stomp.connect({}, function() {
self.socket.subscription = self.socket.stomp.subscribe(channelUrl,
function (result) {
//return angular.fromJson(result.body);
$rootScope.$apply(function() {
callBackFn(result.body);
});
return result.body;
}
);
});
};
})
AngularJS modifies the normal JavaScript flow by providing its own event processing loop. This splits the JavaScript into classical and AngularJS execution context. Only operations which are applied in the AngularJS execution context will benefit from AngularJS data-binding, exception handling, property watching, etc... You can also use $apply() to enter the AngularJS execution context from JavaScript.
Keep in mind that in most places (controllers, services) $apply has already been called for you by the directive which is handling the event. An explicit call to $apply is needed only when implementing custom event callbacks, or when working with third-party library callbacks.
For more information, see
AngularJS Developer Guide - Integration with the browser event loop
This is a very common issue and happens when a 3rd-party library(out of the angular environment) is used with angularjs. In such cases you need to manually trigger a digest cycle using the:
$scope.$apply()
After that all angular bindings will be updated. Using $timeout (even without timeValue) has the same result as it also triggers $apply()
I am trying to use the ebay api in an angular.js app.
The way the api works by itself is to pass data to a callback function and within that function create a template for display.
The problem that I am having is in adding the data returned from the callback to the $scope. I was not able to post a working example as I didnt want to expose my api key, I am hoping that the code posted in the fiddle will be enough to identify the issue.
eBayApp.controller('FindItemCtrl', function ($scope) {
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
}
console.log($scope.items); //this is undefined
})
http://jsfiddle.net/L7fnozuo/
The reason the second instance of $scope.items is undefined, is because it is run before the callback function happens.
The chances are that $scope.items isn't updating in the view either, because Angular doesn't know that it needs to trigger a scope digest.
When you use the Angular provided async APIs ($http, $timeout etc) they have all been written in such a way that they will let Angular know when it needs to update it's views.
In this case, you have a couple of options:
Use the inbuilt $http.jsonp method.
Trigger the digest manually.
Option number 1 is the more sensible approach, but is not always possible if the request is made from someone else's library.
Here's an update to the fiddle which uses $http.jsonp. It should work (but at the moment it's resulting in an error message about your API key).
The key change here is that the request is being made from within Angular using an Angular API rather than from a script tag which Angular knows nothing about.
$http.jsonp(URL)
.success($scope.success)
.error($scope.error);
Option 2 requires you to add the following line to your JSONP callback function:
globalFunc = function(root){
$scope.items = root.findItemsByKeywordsResponse[0].searchResult[0].item || [];
console.log($scope.items); //this shows the data
$scope.$apply(); // <--
}
This method tells Angular that it needs to update it's views because data might have changed. There's a decent Sitepoint article on understanding this mechanism, if you are interested.
Background:
I am building my offline application which uses AngularJS for UI and PocuhDB for locally storing the data retrieved from the server.
Issue:
The data retrieved from PouchDB is not getting rendered in the UI.
Controller:
$scope.retrieveView = function (sys, code, majorVer, minorVer) {
var promise;
promise = dataService.getDataFromLocalDb().then(
function(dataFromPouchDb){
$scope.data = dataFromPouchDb.data;
});
return promise;
}
And then in the UI code I have the following :
<h1> {{data}}</h1>
I have debugged the code and everything seem to work fine. But the data is not getting displayed in the UI.
If I hard code a value to the data field then its getting rendered in the UI
$scope.data ="TEST";
This question is kind a old but I just came around it.
Issue is that Angularjs is based on so called digest cycles. When your model or view is changed digest cycle is triggered, watch for changes and update model or view respectively. It is so called two way data binding.
This digest cycle is not triggered periodically on some time base but on events instead. Those events are angular directives like ng-click, ajax calls $http or some other angular events like $timeout. You can find more information about digest here.
In general you should use those things when working with angular application to avoid such situations. In some cases its not possible however like in your case when getting data from DB. Digest cycle is not triggered and your view is not updated by angular.
Workaround for this is manually trigger $digest cycle. Way you have described:
if(!$scope.$$phase) {
$scope.$digest();
}
is working but considered as angular anti-patern and is discouraged by angular team, you should use:
$timeout();
instead. For more information see this answer.
I would maybe consider adding $timeout() call to hook for insert, update, delete hooks or events. Maybe pouchDB sync could be helpfull there.
The code you show seemed correct, maybe you can use console.log() to track the progress of the data. I think the problem might not in this layer. Maybe in the area where you wrapped getDataFromLocalDb(), track and find if the data have transfer to here, or where it disappeared.
The code started to work when i added the following :
if(!$scope.$$phase) {
$scope.$digest();
}
But i have no idea what magic does this code do.
It would be a great help if some some could advice.
The complete code that works now is :
$scope.retrieveView = function (sys, code, majorVer, minorVer) {
var promise;
promise = dataService.getDataFromLocalDb().then(
function(dataFromPouchDb){
$scope.data = dataFromPouchDb.data;
if(!$scope.$$phase) {
$scope.$digest();
}
});
return promise;
}
I have a custom Web API written in .NET that returns user's information that will be used in my AngularJS application. I want to be able to call my API once, and then use that returned JSON across my entire AngularJS application without having to recall the API within each of my controllers.
I currently have a factory of services, one of which returns all of the client's details I need to use the in rest of the services.
.factory('customApiService', function ($resource) {
return {
userInfo: function(userId, callback){
var api = $resource('../../api/UserInfo/:userId', {
userId: userId
}, {
fetch: 'JSONP',
'query': { isArray: false }
});
api.fetch(function (response) {
callback(response);
});
}
}
)
Now I don't want to call this userInfo service in every controller, but I do want the data to be passed into each without calling my API multiple times.
customApiService.userInfo(userId, function (d) {
var gaProfileId = d.gaProfileId;
var yelpId = d.yelpId;
var tripAdvisorId = d.tripAdvisorId;
var facebookPageName = d.facebookPage;
var twitterHandle = d.twitterHandle;
var clientName = d.clientName;
var searchlightAccountId = d.searchlightAccountId;
var searchlightDomain = d.searchlightDomainId;
}
You can try global variables .
use a $rootScope https://docs.angularjs.org/guide/scope
$rootScope is available in all controllers an templates .Just inject $rootscope in your controller or wherever required.
From what I read of your description and responses to other questions, it sounds like you're trying to make an asynchronous call before the rest of your app starts up. This is possible, but complex, and sort of voids the point of Angular in the first place. As I see it, you have two options:
QUICK HACK: If you really want this kind of behavior, why start your app at all? Do your request first, before you define your app in the first place, then define your app in the result handler for the request.
RIGHT WAY: Alter the behavior of your services and controllers to tolerate not having enough information to fully start. A lot of times this is less difficult than it sounds. Usually you can just chain a promise into their initialization block of code to wait for the data you need. Take a look at Brian Ford's "Angular Modal" project, at the lines of code I've highlighted here:
https://github.com/btford/angular-modal/blob/master/modal.js#L25-L36
This technique sets up a promise to return from the function. If the data it needs is already loaded from the service, it resolves the promise immediately. Otherwise, it makes the call to get what it's after, and you can see later (line 39) that the module uses promise.then() to wait until it has the data it needs to run.
This is a great pattern for both controllers and services when working with asynchronous data.
If using a $resource call instead, note that most $resource calls return a promise in a property called $promise. You can do something like this:
var MyController = function($scope) {
// This will get set soon!
$scope.myData = null;
var myResource = $resource('/path/to/:someId', { someId: '#id' });
myResource.get({ someId: 1 }).$promise.then(function(data) {
$scope.myData = data;
});
};
You can do more things in the .then() resolution callback for the promise, like initialize more parts of your controller, etc. There are also ways you can delay starting your entire controller until the resource is available. One really cool way is if you happen to be using the Angular ui-router module, there is a "resolve" option when defining each route. You can use that to call the $resource as shown above, and ui-router will wait to start your controller/view until it has what it needs.
I'm writing a web application using AngularJS. I use a third-party library (that provides an Angular service) to fetch values from a database, and then use those to initialize some dropdown/select boxes on a page.
So, I have simple select boxes like this:
<div ng-controller="ChoiceCtrl">
<select ng-model="selectedFoo" ng-options="foo in foos"></select>
<select ng-model="selectedBar" ng-options="bar in bars"></select>
</div>
And a corresponding controller that initializes the choices for the select boxes. The service I'm using calls the given callback function after it receives values from the database. (The callback functions could be refactored into one but I'm using separate ones for clarity.)
angular.module('choice').controller('ChoiceCtrl', function($scope, ThirdPartyService) {
$scope.selectedFoo = '';
$scope.selectedBar = '';
$scope.foos = '';
$scope.bars = '';
var fooCallback = function(result) {
$scope.foos = result;
$scope.$apply;
}
var barCallback = function(result) {
$scope.bars = result;
$scope.$apply;
}
ThirdPartyService.asyncGetData('fetchFooOptions', fooCallback);
ThirdPartyService.asyncGetData('fetchBarOptions', barCallback);
});
The database calls are asynchronous and finish after the page has been rendered for the first time, so I manually call $scope.$apply in each callback function.
Is this the right way to initialize dropdown/select boxes in an AngularJS app when the values are fetched asynchronously when loading a page?
I've read tutorials saying that calling $scope.$apply manually is always a "code smell"... But since I'm fetching the values from a database, the operation happens "outside of Angular" which I believe makes those calls justified - and actually necessary.
I'm also wondering if the controller is the right place for these calls. In the tutorials I've read the options are always set in the controller but those sandbox examples never have an asynchronous database call happening.
You should modify three things in your code
The service should return a promise :Refer to documentation of angular for creating promise
Inside service resolve the promise when data is recieved from the server
Inside controller just assign proper values to bar and foos when promise is resolved
Remove $scope.apply since now you are modifying the values inside proper angular scope
Link:Use Promise and service together in Angular