So I have a small angular app that takes in a search query, sends it to an elasticsearch node I've got set up, and then displays the result set on screen.
My problem is that when I make a new query, the results gets appended to the end of the current result set. What I would like it to do is to erase whatever is currently on the page, and reload it with only the new data, much like how searching for something on Google returns a completely new set of results.
Is there any way to do this? Code below for reference.
// this is the controller that displays the reuslts.
var displayController = function($scope, $rootScope, $window, notifyingService) {
var dataReady = function(event, data) {
$scope.resultSet = notifyingService.getData();
}
$rootScope.$on('data-ready', dataReady)
}
app.controller("displayController", ["$scope", "$rootScope", "$window", "notifyingService", displayController]);
// this is the service that's responsible for setting the data
var notifyingService = function($http, $rootScope) {
var svc = {
_data: [],
setData: setData,
getData: getData
};
function getData() {
return svc._data;
}
function setData(data) {
var base_obj = data.hits.hits
console.log("Setting data to passed in data.");
console.log('length of dataset: ' + base_obj.length);
for(var i = 0; i < base_obj.length; i++){
svc._data.push(base_obj[i]._source);
}
$rootScope.$broadcast('data-ready', svc._data);
}
return svc;
};
app.factory("notifyingService", ["$http", "$rootScope", notifyingService]);
In setData just before the loop re-initialize svc._data
svc._data = [];
Clear you svc._data before you start adding the new query.
function setData(data) {
var base_obj = data.hits.hits;
svc._data = [];//reset your array before you populate it again.
for(var i = 0; i < base_obj.length; i++){
svc._data.push(base_obj[i]._source);
}
$rootScope.$broadcast('data-ready', svc._data);
}
Related
myData is generated in myFactory, and the following code works if myData is generated from a single myHttp.GetGroupMember function.
However, it is not working with multiple GetGroupMember calls as they execute "at the same time", and dataContainer gets initialized from createMyData call.
How can I call second GetGroupMember after first GetGroupMember is "finished"?
var myFactory = function (myHttp) {
var myData = [];
var dataContainer = [];
var numProgress;
var onSuccess = function(data_member) {
myData.push(data_member);
numProgress++;
loadMember(dataContainer); // if member is succefully loaded, load next member
// if numProgress == data_member.length, call next GetGroupMember() here?
}
var loadMember = function(data_group) {
if (numProgress < data_group.length) {
myHttp.getMemberDetails(data_group.division, data_group.members[numProgress]).then(onSuccess, onError);
}
}
var createMyData = function(response) {
dataContainer = response; // coded as shown to call loadMember() iteratively
loadMember(dataContainer);
}
var getMyData = function(init) {
myHttp.getGroupMember("Division1", "Group_Foo").success(createMyData); // getGroupMember_1
// how to call getGroupMember_2 after getGroupMember "finished"?
myHttp.getGroupMember("Division2", "Group_Bar").success(createMyData); // getGroupMember_2
return myData;
}
}
var myControl = function($scope, myFactory) {
$scope.myData = myFactory.getMyData();
}
Easiest thing in the world:
myHttp.getGroupMember("Division1", "Group_Foo")
.success(createMyData)
.then(function() {
return myHttp.getGroupMember("Division2", "Group_Bar")
}).success(createMyData);
Read up on promises.
Solved: The below code now works for anyone who needs it.
Create an Angular factory that queries a database and returns query results.
Pass those results of the query into the $scope of my controller
Render the results of the $scope variable in my view (html)
Challenge:
Because this is for a node-webkit (nwjs) app, I am trying to do this without using express, setting up api endpoints, and using the $http service to return the query results. I feel like there should be a more eloquent way to do this by directly passing the data from nodejs to an angular controller. Below is what I've attempted but it hasn't worked.
Database: Below code is for a nedb database.
My Updated Controllers
app.controller('homeCtrl', function($scope,homeFactory){
homeFactory.getTitles().then(function(data){
$scope.movieTitles = data;
});
});
My Updated Factory:
app.factory('homeFactory', function($http,$q) {
return {
getTitles: function () {
var deferred = $q.defer();
var allTitles = [];
db.find({}, function (err, data) {
for (var i = 0, len = data.length; i < len; i++) {
allTitles.push(data[i].title)
}
deferred.resolve(allTitles);
});
return deferred.promise;
}
}
});
HTML
<script>
var Datastore = require('nedb');
var db = new Datastore({ filename: './model/movies.db', autoload: true });
</script>
<--shows up as an empty array-->
<p ng-repeat="movie in movieTitles">{{movie}}</p>
So the problem with your original homeFactory is that db.find happens asynchronously. Although you can use it just fine with callbacks, I think it is better practice to wrap these with $q.
app.service('db', function ($q) {
// ...
this.find = function (query) {
var deferred = $q.defer();
db.find(query, function (err, docs) {
if (err) {
deferred.reject(err);
} else {
deferred.resolve(docs);
}
});
return deferred.promise;
};
// ...
});
Once you begin wrapping non-Angular code like this, then it makes the interface consistent.
app.service('homeFactory', function (db) {
this.getAllTitles = function () {
return db.find({}).then(function (docs) {
return docs.map(function (doc) {
return doc.title;
});
});
};
});
And the controller would look like:
app.controller('homeCtrl', function ($scope, homeFactory) {
homeFactory.getAllTitles().then(function (titles) {
$scope.movieTitles = titles;
});
});
Partial Answer: I was able to get it working by moving all the code into the controller. Anyone now how to refactor the below to use a factory to clean up the code?
Updated Controller ($scope.movietitles is updating in views with the correct data):
$scope.movieTitles = [];
$scope.getMovieTitles = db.find({},function(err,data){
var arr = [];
for (var i = 0, len = data.length; i < len; i++){
arr.push(data[i].title)
}
$scope.movieTitles = arr;
$scope.$apply();
});
I am creating an Ionic application that is pulling articles from a joomla K2 website. I am using $http and just ending my url off with '?format=json' and that is working perfectly. However the website I am pulling data from updates its articles every few minutes so I need a way for the user to be able to refresh the page. I have implemented Ionics pull to refresh and it is working swell except for the fact that instead of just pulling in new articles it just appends all the articles to my array. Is there anyway to just maybe iterate over current articles timestamps or IDs (I am caching articles in localStorage) to just bring in new articles? My factory looks like this:
.factory('Articles', function ($http) {
var articles = [];
storageKey = "articles";
function _getCache() {
var cache = localStorage.getItem(storageKey );
if (cache)
articles = angular.fromJson(cache);
}
return {
all: function () {
return $http.get("http://jsonp.afeld.me/?url=http://mexamplesite.com/index.php?format=json").then(function (response) {
articles = response.data.items;
console.log(response.data.items);
return articles;
});
},
getNew: function () {
return $http.get("http://jsonp.afeld.me/?url=http://mexamplesite.com/index.php?format=json").then(function (response) {
articles = response.data.items;
return articles;
});
},
get: function (articleId) {
if (!articles.length)
_getCache();
for (var i = 0; i < articles.length; i++) {
if (parseInt(articles[i].id) === parseInt(articleId)) {
return articles[i];
}
}
return null;
}
}
});
and my controller:
.controller('GautengCtrl', function ($scope, $stateParams, $timeout, Articles) {
$scope.articles = [];
Articles.all().then(function(data){
$scope.articles = data;
window.localStorage.setItem("articles", JSON.stringify(data));
},
function(err) {
if(window.localStorage.getItem("articles") !== undefined) {
$scope.articles = JSON.parse(window.localStorage.getItem("articles"));
}
}
);
$scope.doRefresh = function() {
Articles.getNew().then(function(articles){
$scope.articles = articles.concat($scope.articles);
$scope.$broadcast('scroll.refreshComplete');
});
};
})
Use underscore.js to simple filtering functionality.
For example:
get all id's of already loaded items (I believe there is some unique fields like id)
http://underscorejs.org/#pluck
var loadedIds = _.pluck($scope.articles, 'id');
Reject all items, if item.id is already in loadedIds list.
http://underscorejs.org/#reject
http://underscorejs.org/#contains
var newItems = _.reject(articles, function(item){
return _.contains(loadedIds, item.id);
});
Join new items and existings:
$scope.articles = newItems.concat($scope.articles);
or
http://underscorejs.org/#union
$scope.articles = _.union(newItems, $scope.articles);
Actually _.union() can manage and remove duplicates, but I would go for manual filtering using item.id.
I am using SharePoint's JavaScript Object Model in an AngularJS app and need one of its functions to execute on page load so an array is populated and used by an ng-repeat.
Currently, it logs to the console the array push on page load, but does not appear to commit the values into the array for use on the page/$scope until I click in/then out of a input box, click OK on an alert box, or a dummy link that keeps me on the page. This is where I am confused as to why it can log the array properly on load, but not have the array ready in $scope for the page.
I have tried the following, but without success:
1) I have moved the call to execute the function to the bottom of the controller
2) I have tried angular.element(document).ready
3) I have tried wrapping it in a function such as:
$scope.initTaxonomy = function () {
$(function () {
// Function here
});
};
What I don't understand is why the following, which calls to a REST service, executes on page load/works perfectly by pushing into $scope for use in an ng-repeat, while my function doesn't:
// Array holding items for display on homepage
$scope.items = [];
appItems.query(function (result) {
// Data is within an object of "value", so this pushes the server side array into the $scope array
var result = result.value;
var userInfo;
// Foreach result, push data into items array
angular.forEach(result, function (resultvalue, resultkey) {
$scope.items.push({
title: resultvalue.Title,
status: resultvalue.Status
});
});
});
This is the function that will successfully log to the console on page load, but won't populate the array in my ng-repeat until I click around on the page:
var termsArray = [];
$scope.termsArray = termsArray;
execOperation();
function execOperation() {
//Current Context
var context = SP.ClientContext.get_current();
//Current Taxonomy Session
var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
//Term Stores
var termStores = taxSession.get_termStores();
//Name of the Term Store from which to get the Terms.
var termStore = termStores.getByName("Taxonomy_1111");
//GUID of Term Set from which to get the Terms.
var termSet = termStore.getTermSet("1111111");
var terms = termSet.getAllTerms();
context.load(terms);
context.executeQueryAsync(function () {
var termEnumerator = terms.getEnumerator();
while (termEnumerator.moveNext()) {
var currentTerm = termEnumerator.get_current();
var guid = currentTerm.get_id();
var guidString = guid.toString();
termsArray.push({
termName: currentTerm.get_name(),
termGUID: guidString,
});
//getLabels(guid);
}
console.log($scope.termsArray)
}, function (sender, args) {
console.log(args.get_message());
});
};
My controller is loaded with the page via app.js as follows:
$routeProvider.
when('/', { templateUrl: '/views/home.html', controller: 'appHomeItemsCtrl' }).
when('/add-item', { templateUrl: '/views/add-item.html', controller: 'appItemPostCtrl' }).
otherwise({ redirectTo: '/' });
Is this an issue with SharePoint's JSOM needing something to execute properly, how I am trying to execute in AngularJS, or something else?
I was able to get this working. The problem was that Angular doesn't appear to respect non-Angular functions, so using $scope.apply around the push of my array to $scope was effective. Note that wrapping the entire function caused issues with the digest of Angular:
var termsArray = [];
execOperation();
function execOperation() {
//Current Context
var context = SP.ClientContext.get_current();
//Current Taxonomy Session
var taxSession = SP.Taxonomy.TaxonomySession.getTaxonomySession(context);
//Term Stores
var termStores = taxSession.get_termStores();
//Name of the Term Store from which to get the Terms.
var termStore = termStores.getByName("Taxonomy_1111");
//GUID of Term Set from which to get the Terms.
var termSet = termStore.getTermSet("1111");
var terms = termSet.getAllTerms();
context.load(terms);
context.executeQueryAsync(function () {
var termEnumerator = terms.getEnumerator();
while (termEnumerator.moveNext()) {
var currentTerm = termEnumerator.get_current();
var guid = currentTerm.get_id();
var guidString = guid.toString();
termsArray.push({
termName: currentTerm.get_name(),
termGUID: guidString,
});
}
console.log(termsArray)
$scope.$apply(function () {
$scope.termsArray = termsArray;
});
}, function (sender, args) {
console.log(args.get_message());
});
};
I have two controllers which perform very similar tasks.
What's the appropriate way to remove code duplication?
Initially I tried to move create a template method as an angular service, but it looks like I cannot inject $scope to services.
.controller("TspController", function($scope, $rootScope, $routeParams, $location, ProblemsLoader) {
$scope.problemType = "tsp";
var problemId = $routeParams.problemId;
ProblemsLoader.loadIndex("tsp", function(index) {
if (problemId) {
$scope.problems = index;
ProblemsLoader.loadProblem("tsp", problemId, function(problem) {
$scope.problem = problem
});
} else {
var firstProblemId = index[0].fileName;
$location.path("/tsp/" + firstProblemId)
}
});
})
.controller("VrpController", function($scope, $rootScope, $http, $routeParams, ProblemsLoader) {
$scope.problemType = "vrp";
var problemId = $routeParams.problemId;
ProblemsLoader.loadIndex("vrp", function(index) {
if (problemId) {
$scope.problems = index;
ProblemsLoader.loadProblem("vrp", problemId, function(problem) {
$scope.problem = problem
});
} else {
var firstProblemId = index[0].fileName;
$location.path("/vrp/" + firstProblemId)
}
});
});
Actually, services are a good solution for this use case, and injecting $scope isn't necessary.
When a service exposes an object property, implicit watches are set up on them, so state changes get propogated transparently.
Service:
var problemSvc = function($http) {
var problemData = {
problemId: 1,
problemField: '',
otherProblemField: ''
};
return {
problem: problemData, // exposing the data as object
loadProblem: function(problemId) {
// load logic here
problemData.problemField = 'load data here';
problemdata.otherProblemField = 'more data from server or whatever';
}
}
}
angular.service('problemSvc', ['$http', problemSvc]);
1-N consuming controllers:
angular.controller('ctrl', ['$scope', 'problemSvc', function($scope, problemSvc) {
$scope.problem = problemSvc.problem;
}]);
As fields change in your problem service, any controller where the service is injected will be updated automatically
<div>{{problem.problemField}}</div>