I have a checkbox, like:
<input type="checkbox" ng-model="isPreCheckIn" />
I'm getting isPreCheckin (boolean) from a service which uses $q and either returns from the server or localStorage (if it exists).
The call in the controller looks like:
deviceSettings.canCheckIn().then(function (canCheckIn) {
$scope.isPreCheckin = !canCheckIn ? true : false;
});
And deviceSettings.canCheckIn looks like:
function canCheckIn() {
var dfrd = $q.defer();
LoadSettings().then(function (success) {
return dfrd.resolve(localStorage.canCheckIn);
});
return dfrd.promise;
};
So, on first page load, the checkbox doesn't bind correctly to isPreCheckIn; in fact, if I do a {{isPreCheckIn}}, it doesn't either. If I switch off of that page and go back, it works.
It appears that canCheckIn is outside of angular, based on that assumption, you need to wrap your assignment within $scope.apply:
deviceSettings.canCheckIn().then(function (canCheckIn) {
$scope.$apply(function(){
$scope.isPreCheckin = !canCheckIn ? true : false;
});
});
This tells angular to recognize the changes on your $scope and apply to your UI.
I think you should wrap the following in a $apply:
function canCheckIn() {
var dfrd = $q.defer();
LoadSettings().then(function (success) {
scope.$apply(function() {
dfrd.resolve(localStorage.canCheckIn);
}
});
return dfrd.promise;
};
It sounds like a timing issue. You may need to put a resolve clause in your route to give this call time to run and then pass in the result as a DI value. Without knowing which router you are using it is impossible to give you an accurate answer, but you might look at the video on egghead.io regarding routes and resolve.
Related
I have tried various ways but nothing works for me. What I have:
app.controller('someCtrl',
...
$scope.load = function () {
client.loadAgreements().
then(function (response) {
$scope.unapprovedCount = response.data[StatusTypeEnum.Unapproved];
$scope.approvedCount = response.data[StatusTypeEnum.Approved];
$scope.$apply();
});
};
Html part:
<button text="'({{unapprovedCount}})'" ...
<button text="'({{approvedCount}})'"...
When I do:
app.controller('someCtrl',
...
$scope.unapprovedCount = 777;
$scope.load = function () {
client.loadAgreements().
then(function (response) {
$scope.unapprovedCount = response.data[StatusTypeEnum.Unapproved];
$scope.approvedCount = response.data[StatusTypeEnum.Approved];
$scope.$apply();
});
};
I can see 777 when page renders. However I don't see any updates from promise. Variable is assigned but view is not updated. And if I add apply method then I am getting error:
Error: [$rootScope:inprog] $digest already in progress
What else can I try, as I am new to angular and don't know how to do this?
EDIT:
I am not sure if this matters but actually this button uses directive:
<button ng-fas-button-with-color-indicator text"'Approved ({{unapprovedCount}})'" color="red" ...
And text is rendered in some div.
Remove
$scope.$apply();
from your code and check in your console the value of
response.data[StatusTypeEnum.Unapproved]
Why you need the $scope.$apply()?
You have to remove it from your code.
The only need of this function is when you are using a third party lib to manipulate variable from the scope, outside of angular's watch.
When you use apply and the digest cycle is ok, nothing changed, will raise this error.
I need to transform objects coming from $http call to an api. My code adds some fields (functions) to the object coming from the api, here the constructor of this object :
(function () {
window.TransformedObject = function (obj) {
var self = this;
self = {};
if (obj) {
self = angular.copy(obj);
}
self.hasChanged = function () {
// return true or false if the object has changed
}
return self;
}
}());
The $http transform code looks like this :
$http({
url: 'api/...',
method: 'GET',
transformResponse: function(value) {
return new TransformedObject(JSON.parse(value));
})
}).success(function(data){
vm.obj = angular.copy(data);
});
Note that the value in the transformResponse callback is stringified, and need to be parsed to get the object
All this is working fine, suppose the object coming from the api contains a key called title, doing obj.title = 'some title' will update the object.
The problem :
Binding the title field with an input tag will not update the object if the change is coming from the view.
I use a regular ng-model to do it:
<input type="text" placeholder="Title" ng-model="vm.obj.title"/>
even using $rootScope.$watch will never be triggered if the change is coming from the view aka the input tag.
$rootScope.$watch(function () {
return vm.obj;
}, function () {
console.log('watch');
// this log will never appear in the console
});
Am I doing something wrong, why transforming the object coming from the api is breaking angulars binding ???
Thanks.
http://www.bennadel.com/blog/2605-scope-evalasync-vs-timeout-in-angularjs.htm
Sometimes, in an AngularJS application, you have to explicitly tell
AngularJS when to initiate it's $digest() lifecycle (for dirty-data
checking). This requirement is typically contained within a Directive;
but, it may also be in an asynchronous Service. Most of the time, this
can be easily accomplished with the $scope.$apply() method. However,
some of the time, you have to defer the $apply() invocation because it
may or may not conflict with an already-running $digest phase. In
those cases, you can use the $timeout() service; but, I'm starting to
think that the $scope.$evalAsync() method is a better option.
...
Up until now, my approach to deferred-$digest-invocation was to
replace the $scope.$apply() call with the $timeout() service (which
implicitly calls $apply() after a delay). But, yesterday, I discovered
the $scope.$evalAsync() method. Both of these accomplish the same
thing - they defer expression-evaluation until a later point in time.
But, the $scope.$evalAsync() is likely to execute in the same tick of
the JavaScript event loop.
I need to change the order of scope, save but me back an error that save() is not a function.
I'm using restangular to create the objects.
The function is triggered Onsort, I tried using http, but also gives me error.
$scope.onChange = function() {
ApiRestangular.all($scope.section).getList($scope.query).then(function(res){
$scope.items = res;
order = ApiRestangular.copy(res);
console.log(order);
$scope.sortOptions = {
animation : 150,
onSort: function(){
order.put().then(function(){
toast.msgToast($scope.section+ ' ...Ordem atualizada!');
});
}
};
});
};
There are a few issues here but I think the biggest one is that "save()" really isn't a function. I believe the correct syntax is $save().
Here is the documentation.
https://docs.angularjs.org/api/ngResource/service/$resource
Without seeing the rest of your controller code it is tough to say what other problems there may be (dependency injection?). But this should get your started.
I've tried to strip out the details and make this fairly generalized...
Using 1.2 rc2 my code worked fine, after updating to 1.2 stable and correcting for $parse changes I've run into a binding problem. Before the update, the following code worked without any issues. updateChildObject() gets called from the html page.
.when('/the-page/', {
controller: function($scope, serviceResults, FactoryService) {
$scope.object.childObject = serviceResults;
// this function used to work. Now assigns the function to the
// scope rather than the results
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
},
resolve: {
serviceResults: function(FactoryService) {
return FactoryService.getSomethingFromServer(args);
}
}
Since this is failing now ($scope.object.childObject appears to get set as the function and not the results) I believe the appropriate way to solve it is through a promise. (Note, the service itself is using a promise successfully.) However, I'm having difficulty getting the $scope to update when the promise is resolved.
I believe the following code is along the right track. $q is injected in the controller.
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function() {
return FactoryService.getSomethingFromServer(args);
});
$scope.object.childObject = defer.resolve();
};
...
So can anyone tell my what I'm doing wrong here? Promises are just one of those things that haven't really clicked for me yet.
Just as an alternative to your answer: you say FactoryService is already successfully using a promise, and in that case it seems like you don't need an additional promise in updateChildObject too. You could update FactoryService.getSomethingFromServer(args) to return a promise (i.e. with return defer.promise; at the end and defer.resolve(results); in the async bit), and then simplify updateChildObject to just:
$scope.updateChildObject = function(args) {
FactoryService.getSomethingFromServer(args).then(function(results) {
$scope.object.childObject = results;
}
};
Also, it's worth knowing that Angular 1.2 intentionally breaks automatic promise unwrapping that was in earlier versions: https://github.com/angular/angular.js/issues/4158 . It used to be the case that this code
$scope.updateChildObject = function(args) {
$scope.object.childObject = FactoryService.getSomethingFromServer(args);
};
would work identically to the one above (assuming getSomethingFromServer returns a promise), but not anymore. This might be the issue you're running into with 1.2
Figured out what I was doing wrong. Definitely was a promise issue in that I just wasn't using them correctly. The following solved it:
...
$scope.updateChildObject = function(args) {
var defer = $q.defer();
defer.promise.then(function(results) {
$scope.object.childObject = results;
});
defer.resolve(FactoryService.getSomethingFromServer(args));
};
...
So defer.resolve calls what's to be resolved. promise.then() passes the results to the next action. So simple.
I am writing a small Angular web application and have run into problems when it comes to loading the data. I am using Firebase as datasource and found the AngularFire project which sounded nice. However, I am having trouble controlling the way the data is being displayed.
At first I tried using the regular implicit synchronization by doing:
angularFire(ref, $scope, 'items');
It worked fine and all the data was displayed when I used the model $items in my view. However, when the data is arriving from the Firebase data source it is not formatted in a way that the view supports, so I need to do some additional structural changes to the data before it is displayed. Problem is, I won't know when the data has been fully loaded. I tried assigning a $watch to the $items, but it was called too early.
So, I moved on and tried to use the angularfireCollection instead:
$scope.items = angularFireCollection(new Firebase(url), optionalCallbackOnInitialLoad);
The documentation isn't quite clear what the "optionalCallbackOnInitialLoad" does and when it is called, but trying to access the first item in the $items collection will throw an error ("Uncaught TypeError: Cannot read property '0' of undefined").
I tried adding a button and in the button's click handler I logged the content of the first item in the $items, and it worked:
console.log($scope.items[0]);
There it was! The first object from my Firebase was displayed without any errors ... only problem is that I had to click a button to get there.
So, does anyone know how I can know when all the data has been loaded and then assign it to a $scope variable to be displayed in my view? Or is there another way?
My controller:
app.controller('MyController', ['$scope', 'angularFireCollection',
function MyController($scope, angularFireCollection) {
$scope.start = function()
{
var ref = new Firebase('https://url.firebaseio.com/days');
console.log("start");
console.log("before load?");
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
console.log("start() out");
};
$scope.start();
//wait for changes
$scope.$watch('items', function() {
console.log("items watch");
console.log($scope.items[0]); //undefined
});
$scope.testData = function()
{
console.log($scope.items[0].properties); //not undefined
};
}
]);
My view:
<button ng-click="testData()">Is the data loaded yet?</button>
Thanks in advance!
So, does anyone know how I can know when all the data has been loaded
and then assign it to a $scope variable to be displayed in my view? Or
is there another way?
Remember that all Firebase calls are asynchronous. Many of your problems are occurring because you're trying to access elements that don't exist yet. The reason the button click worked for you is because you clicked the button (and accessed the elements) after they had been successfully loaded.
In the case of the optionalCallbackOnInitialLoad, this is a function that will be executed once the initial load of the angularFireCollection is finished. As the name implies, it's optional, meaning that you don't have to provide a callback function if you don't want to.
You can either use this and specify a function to be executed after it's loaded, or you can use $q promises or another promise library of your liking. I'm partial to kriskowal's Q myself. I'd suggest reading up a bit on asynchronous JavaScript so you get a deeper understanding of some of these issues.
Be wary that this:
$scope.items = angularFireCollection(ref, function()
{
console.log("loaded?");
console.log($scope.items[0]); //undefined
});
does correctly specify a callback function, but $scope.items doesn't get assigned until after you've ran the callback. So, it still won't exist.
If you just want to see when $scope.items has been loaded, you could try something like this:
$scope.$watch('items', function (items) {
console.log(items)
});
In my project I needed to know too when the data has been loaded. I used the following approach (implicit bindings):
$scope.auctionsDiscoveryPromise = angularFire(firebaseReference.getInstance() + "/auctionlist", $scope, 'auctionlist', []);
$scope.auctionsDiscoveryPromise.then(function() {
console.log("AuctionsDiscoverController auctionsDiscoveryPromise resolved");
$timeout(function() {
$scope.$broadcast("AUCTION_INIT");
}, 500);
}, function() {
console.error("AuctionsDiscoverController auctionsDiscoveryPromise rejected");
});
When the $scope.auctionsDiscoveryPromise promise has been resolved I'm broadcasting an event AUCTION_INIT which is being listened in my directives. I use a short timeout just in case some services or directives haven't been initialized yet.
I'm using this if it would help anyone:
function getAll(items) {
var deferred = $q.defer();
var dataRef = new Firebase(baseUrl + items);
var returnData = angularFireCollection(dataRef, function(data){
deferred.resolve(data.val());
});
return deferred.promise;
}