How to set a variable in an AngularJS callback? - angularjs

I have an AngularJS 1.5 directive:
var assetSearchService = function(proService) {
var assets = [];
var searchAssets = function(searchTerm){
proService.searchAssets(searchTerm).then(function(data){
assets = data.data;
});
};
return {
searchAssets, searchAssets,
assets: assets
};
};
When I try to use assetSearchService.assets in my controller after calling search, the data is not set in assetService.assets.
If I log the data after the searchAssets promise returns, I am getting data.
this.assets does not work so how do I do get the variable back from the callback?

Found an answer. Neither assets = newArray nor conact does not work because both return a new array and break the reference, rather than modifying the current one.
This works:
Array.prototype.push.apply(assets, data.data);
Here's a working example: https://jsfiddle.net/mbaranski/5k4bqo0z/

You can achieve more complex solution
var assetSearchService = function(proService) {
service = this;
service.assets = [];
service.searchAssets = function(searchTerm){
proService.searchAssets(searchTerm).then(function(data){
assets.push(data.data);
});
};
return service;
};

Related

How can I save data in forEach loop?

I create a forEach loop. I can access data from Firebase with this loop and I can change variable. But I can not save these changes on Firebase. How can I save these changes? Here's my code:
var posts = $firebaseArray(ref);
posts.$loaded().then(function(item){
item.forEach(function(childSnapshot){
var num = childSnapshot.point-1;
childSnapshot.lastPoint = num;
});
});
You're using the AngularFire framework, which builds UI bindings on top of Firebase's regular JavaScript SDK. You should only be using it for things that you're binding to the Angular UI elements. For everything else, you're likely better off using Firebase's regular JavaScript SDK.
If I understand your requirement correctly, you're trying to loop over all child nodes in a database location once and modify a property. If so:
ref.once('value', function(snapshot) {
snapshot.forEach(function(childSnapshot) {
var child = childSnapshot.val();
childSnapshot.ref().update({ lastPoint: child.point - 1 });
});
});
The relevant sections of the Firebase documentation are on reading data once and updating data.
Since AngularFire is built on top of Firebase's JavaScript SDK, they work perfectly together. So if elsewhere you bind the posts to the scope ($scope.posts = $firebaseArray(ref)), they will be updated automatically when you update the last point with the above snippet.
You can create a service that contains a getter and a setter. It would look something like this;
angular.module('app').factory("DataHandler",
function() {
var data;
return {
get: function() {
return data;
},
set: function(something) {
data = something;
}
}
}
);
Then in your code you would call:
var posts = $firebaseArray(ref);
posts.$loaded().then(function(item){
item.forEach(function(childSnapshot){
var num = childSnapshot.point-1;
childSnapshot.lastPoint = num;
DataHandler.set(childSnapshot);
});
});
Then wherever/whenever you need to get that data just call:
Datahandler.get();

View not updating - angular, firebase

This function fires once at page load, but then never again. I've tried $watch, $apply, and firebase's ref.on('value', ... but no dice. The model changes for the value of scope.job.getApplicants(), but scope.applicants_ is not refreshing when this happens.
function go() {
var p = [];
// scope.job.getApplicants() returns a $firebaseArray of user IDs
scope.job.getApplicants().$loaded().then(function(list) {
list.forEach(function(app) {
// User_(app.$id) returns a $firebaseObject
var user = User_(app.$id);
p.push(user);
})
});
return p;
}
scope.applicants_ = go();
This is how I resolved the problem:
ref.on('value', function(apps) {
var applicantIDs = Object.keys(apps.val());
scope.applicants_ = applicantIDs.map(function(app) {
return User_(app);
});
});

Using angular functions ($q) in protractor

I want to use the $q service of angular in my e2e tests. (I want to get the texts of a bunch of elements via getText() which returns a promise. After all promises are resolved, I want to test the list. So I want to use $q.all() etc.)
angular.injector(['myApp']).get('$q'); results in "ReferenceError: angular is not defined"
Installing angular via node and then var angular = require("angularjs"); results in "Error: Cannot find module 'angular'"
Also, inserting a browser.waitForAngular() does not help there.
Using the inject(function($q) {}) syntax has the same problem.
How can I use such angular functions in protractor?
edit:
Here's the very naive version of what I want to achieve
var collectEntries = function(containers) {
var entries = {};
containers.each(function (container) {
var title = container.element(by.tagName('h2'));
title.getText().then(function (text) {
var key = getSomeKey();
var entry = processEntry(text);
entries[key] = entry;
});
});
return entries;
};
That works in principle, at some point in time entries contains all data. However, I need to wait for that moment. What I would do is create and return a promise that gets resolved as soon as all getText promises are resolved.
e.g.
var deferred = $q.defer();
$q.all(getTextPromises).then(function () {
deferred.resolve(entries);
});
return deferred.promise;
From the looks of your code containers is a list of elementFinders? (i.e. var containers = [element(by.x), element(by.y), element(by.z)]):
Using q: (you need to add q as dependency in package.json first)
var q = require('q');
var collectEntries = function(containers) {
var entries = {};
containers.each(function (container) {
var deferred = q.defer();
var title = container.element(by.tagName('h2'));
title.getText().then(function (text) {
deferred.resolve(processEntry(text));
});
entries[getSomeKey()] = deferred.promise();
});
return q.all(entries);
};
expect(collectEntries).toBe({key1: 'title1', key2: 'title2'})
But protractor knows promise itself (and it's preferably that you don't mix protractor's promise with q promise unless you know what you're doing):
var collectEntries = function(containers) {
var entries = {};
containers.each(function (container) {
entries[getSomeKey()] = container.element(by.tagName('h2')).
getText().then(function (text) {
return processEntry(text);
});
});
return protractor.promise.fullyResolved(entries);
};
expect(collectEntries).toBe({key1: 'title1', key2: 'title2'})
If your containers are found using a single selector (i.e. var containers = element.all(by.xyz)), it's even easier:
var collectEntries = function(containers) {
return containers.reduce(function(entries, elem) {
return elem.getText().then(function(text) {
entries[getSomeKey()] = processEntry(text);
return entries;
});
}, {});
};
expect(collectEntries).toBe({key1: 'title1', key2: 'title2'})

angularjs binding/scope issue for select list?

OK switching my code to angularjs and the angular 'way', not sure what I am doing wrong.
A select list is not getting updated when the model changes unless I call $apply, and I find myself calling apply a lot.
index.html has this:
<div id='rightcol' data-ng-include="'partials/rightSidebar.html'"
data-ng-controller="rightSidebarController">
</div>
and rightSidebar.html has this:
<select id='srcList' size='10'
data-ng-model="data.source"
data-ng-click='srcOnclick()'
data-ng-options="s.title for s in data.srcList | filter:{title:data.srcFilter} | orderBy:'title'"></select>
rightSidebarController.js has this:
$scope.data = {};
$scope.data.srcList = dataProvider.getSourceList();
$scope.data.source = dataProvider.getSource();
dataProvider is a service that makes an asynchronous database call (IndexedDB) to populate srcList, which is what gets returned in dataProvider.getSource().
Is it the asynchronous database call that forces me to call $apply, or should the controller be ignorant of that?
Is there a 'better' way to do this?
Edited to add service code.
Another controller calls dataProvider.refreshSourceList:
myDB.refreshSourceList = function() {
myDB.getRecords("source", function(recs) {
myDB.srcList = recs;
$rootScope.$broadcast('SrcListRefresh');
});
};
myDB.srcList is the field being bound by $scope.data.srcList = dataProvider.getSourceList();
myDB.getRecords:
myDB.getRecords = function(storeName, callback) {
var db = myDB.db;
var recList = [];
var trans = db.transaction([storeName], 'readonly');
var store = trans.objectStore(storeName);
var cursorRequest = store.openCursor();
cursorRequest.onerror = myDB.onerror;
cursorRequest.onsuccess = function(e) {
var cursor = cursorRequest.result || e.result;
if (cursor === false || cursor === undefined) {
if (callback !== undefined) {
$rootScope.$apply(function() {
callback(recList);
});
}
} else if (cursor.value !== null) {
recList.push(cursor.value);
cursor.continue();
}
};
cursorRequest.onerror = myDB.onerror;
};
Anything you do async needs to be wrapped in $scope.$apply(). This is because angular works in a similar fashion to a game loop, however instead of constantly running, it knows to end the loop when an action is taken, and $scope.$digest() is called.
If you are using IndexedDB, I would recommend creating an angular wrapper for it, like so:
(forgive my IndexedDB code, I'm not experience with it)
angular.module('app',[])
.factory('appdb', function($rootScope){
var db = indexedDB.open('appdb', 3);
return {
get : function(table, query, callback) {
var req = db.transaction([table])
.objectStore(table)
.get(query);
req.onsuccess(function(){
$rootScope.$apply(function(){
callback(req.result);
});
});
}
};
});
This way you can be sure that any data retrieve and set on a controller scope inside of callback will have $scope.$digest() called afterward.

Can't get waiting for function to return (with promises?) working in angular controller

I'm trying to get the following findTimelineEntries function inside an Angular controller executing after saveInterview finishes:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId}, function() {
$scope.findTimelineEntries();
});
};
The save action adds or edits data that also is part of the timeline entries and therefore I want the updated timeline entries to be shown.
First I tried changing it to this:
$scope.saveInterview = function() {
var functionReturned = $scope.interviewForm.$save({employeeId: $scope.employeeId});
if (functionReturned) {
$scope.findTimelineEntries();
}
};
Later to this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
$scope.saveInterview.done(function(result) {
$scope.findTimelineEntries();
});
And finaly I found some info about promises so I tried this:
$scope.saveInterview = function() {
$scope.interviewForm.$save({employeeId: $scope.employeeId});
};
var promise = $scope.saveInterview();
promise.done(function() {
$scope.findTimelineEntries();
});
But somehow the fact that it does work this way according to http://nurkiewicz.blogspot.nl/2013/03/promises-and-deferred-objects-in-jquery.html, doesn't mean that I can use the same method on those $scope.someFuntcion = function() functions :-S
Here is a sample using promises. First you'll need to include $q to your controller.
$scope.saveInterview = function() {
var d = $q.defer();
// do something that probably has a callback.
$scope.interviewForm.$save({employeeId: $scope.employeeId}).then(function(data) {
d.resolve(data); // assuming data is something you want to return. It could be true or anything you want.
});
return d.promise;
}

Resources