I have a search field. I would like to sendout the search query after a certain delay (eg 2000 ms)
Here is relevant angularjs code that I have managed to produce.
But I am certain there must be a better pattern to do this...
function SearchController($scope, $http, $timeout){
var update_results = function(originalWord){
if (originalWord == $scope.search_term){
$http.get("/search/"+ $scope.search_term).success(function(data){
$scope.search_results = data;
});
}
};
$scope.search = function(){
var originalWord = $scope.search_term.replace("xxxx", "xxxx"); //UGLY HACK TO CLONE A STRING
$timeout(function(){
update_results(originalWord);
}, 2000);
};
}
the fact that you used $timeout is great.
I've seen lots of angularJS codes and all of them use the same mechanism to fire up an event after a certain amount of time.
Related
Iam trying to create a custom filter to filter matching array of values in angularjs. Array Structure below
["tag1","tag2"]
Now I need to filter all objs having tags matching id1,id2.. Below is the filter I have tried
var autoFilter = angular.module("autoFilters",[]);
autoFilter.filter('arrayData', function (){
return function(){
return ["id1","id2"];
}
//$scope.arrayValues = ["id1","id2"];
});
and UI code below
<li style="cursor:pointer" ng-cloak class="list-group-item" ng-repeat="values in suggestionResults | arrayData">{{values.id}} -- {{values.title}}</li>
But Data is not showing up. Can you help me out where Iam doing wrong. Plunker Code available below
plunker here
see the code below :) This is not the best approach in my opinion and will definitely have some performance issue with larger lists, but it does the work (now I used indexOf(2) but there you can pass any truthy/falsy argument)
var autoFilter = angular.module("autoFilters",[]);
autoFilter.controller("filterController",['$scope','$http', function ($scope,$http) {
$scope.searchSuggest = function(){
//$http({method: 'GET', url: 'json/searchSuggestions.json'}).success(function(data) {
$http.get("assets.json").then(function(response) {
//var str = JSON.stringify(response);
//var arr = JSON.parse(str);
$scope.suggestionResult = response.data;
console.log($scope.suggestionResult);
//$scope.arrayData = ["asset_types:document/data_sheet","asset_types:document/brochure"];
}).catch(function activateError(error) {
alert('An error happened');
});
}
$scope.showProduct = function(){
}
}]);
autoFilter.filter('arrayData', function (){
return function(data){
// if you are using jQuery you can simply return $.grep(data, function(d){return d.id.indexOf('2') >-1 });
return data.filter(function(entry){
return entry.id.indexOf('2') > -1
})
}
});
Having experienced working with large lists I would, however, suggest you to avoid using a separate filter for this and rather manipulate it in the .js code. You could easily filter the data when you query it with your $http.get like:
$scope.suggestionResult = response.data.filter(function(){
return /* condition comes here */
}
This way you are not overloading the DOM and help the browser handling AngularJS's sometimes slow digest cycle.
If you need it to be dynamic (e.g. the filtering conditions can be changed by the user) then add an ng-change or $watch or ng-click to the modifiable information and on that action re-filter $scope.suggestionResult from the original response.data
I have a text input below, bound to a model req.mod1, with a debounce delay in updating model, that calls a pullData() function.
<input type="text" class="form-control" ng-change="pullData()" ng-model-options="{debounce: 1000}" ng-model="req.mod1">
Inside of pullData() I have a simple $http.get request, that pulls data and updates some other fields in the form.
$scope.pullData = function(){
var pullingData = true;
if (!pullingData){
$http.get('example.com/search/' + $scope.req.mod1 ).then(res)
...
$scope.req.mod2 = res.data.mod2;
$scope.req.mod3 = res.data.mod3;
var pullingData = false;
...
}
}
The problem that arises is that multiple calls stack on top of each other -- I think -- and so if a user happens to input text, wait >1second, and type some more, the call would go out with the first inputted text. Then it would pull the data and update the form with the first res. I'm trying to keep track of the request with a pullingData var.
Any ideas on how to handle a truly dynamic search call? Is there a way to cancel requests if a new one comes in? Or maybe just tell angular to constantly overwrite it?
Thanks in advance.
I think this is what you need
http://odetocode.com/blogs/scott/archive/2014/04/24/canceling-http-requests-in-angularjs.aspx
When you create a request.. it's called Promise, so what you need to cancel is that.
Something like this:
app.factory("movies", function($http, $q){
var getById = function(id){
var canceller = $q.defer();
var cancel = function(reason){
canceller.resolve(reason);
};
var promise = $http.get("/api/movies/slow/" + id, { timeout: canceller.promise})
.then(function(response){
return response.data;
});
return {
promise: promise,
cancel: cancel
};
};
return {
getById: getById
};
});
When the user enters something to search, your app should always search by the user's input. It means that you shouldn't cancel the user's input.
For example, the user want to search something about 'starwar', when he/she enter 'star', and you get some result. If now you cancel the 'war' which is entered after, the result is not good. So just let Angular override the result.
Moreover, some errors in your example of code, when you call pullData, it never passes the if check:
$scope.pullData = function(){
var pullingData = true;
// !pullingData is always false
if (!pullingData){
$http.get('example.com/search/' + $scope.req.mod1 ).then(res)
}
}
I am using three Angular controllers:
**Controller1**
var fetchStudentDetails = function(){
var sDetails = myService.getList(//url-1 here);
sDetails.then(function (data) {
$scope.studentData = data.list;
var studentId = $scope.studentData[0].id;
});
}
fetchStudentDetails();
$scope.loadSecondLevel = function(){
$state.go('secondLevel');
}
**Controller2**
var fetchClassDetails = function(){
var sDetails = myService.getList(//url-2 here);
sDetails.then(function (data) {
$scope.classData = data.list;
var className = $scope.classData[0].name;
});
}
fetchClassDetails();
$scope.loadThirdLevel = function(){
$state.go('thirdLevel');
}
**Controller3**
$scope.putStudentDetails = function(){
// Here I need studentId,className for updateResource
var sDetails = myService.updateResource(//url-3 here);
sDetails.then(function (data) {
});
}
Where I have to pass studentId (in Controller1), className (in Controller2) into a function which in Controller3. I tried with $rootScope, it is working but when refresh the page $rootScope values become empty. Does anyone know how to do this?
Your question could be split into two aspects:
1. How to share data between controllers
The best practice to share data in Angular 1.x is using factory, store the shared data in a factory service, and expose access methods to controllers:
factory('DetailData', function(myService, $q){
var _details;
function __getDetailData(){
return details
}
function __setDetailData(){
return myService.getList().then(function(data){
_details = data;
})
}
return {
getDetailData: __getDetailData,
setDetailData: __setDetailData
}
})
controller('myContrller', function(DetailData, $scope){
$scope.data = DetailData.getDetailData();
})
2. How to persist data when page refreshed,
you can use localStorage to keep data persistent during page reloading, many tools & libraries can achieve this, for example ngStorage, or you could reset the data from server every time your angular application started:
//this would register work which would be performed
//when app finish loading and ready to start.
angular.module('app').run(function(DetailData){
DetailData.setDetailData();
})
Depending on what problem you are solving.
There are three options:
Is to save data to $rootScope
Is to use $scope.$emit & $scope.$on functions.
Use a custom Service to store the data
And if you need to save data, so it was available after full page reload - localStorage.
Hey this question are responded in Passing data between controllers in Angular JS?
But the simple response is in the services.
I am trying to use a highcharts chart and I want to "simulate" live data coming in, so, when a user presses the "start live stream" button it activates a function from I suppose JavaScript on the web page and then calls the angular controller function that has around 10 second delay.
The way I can query the json data from the controller is from an http request and I use how far back in weeks I want to query the data (I have as far back as 100 weeks). So I want to have a function on the web page the starts at 99 and 100 and pass in the variable to the angular function to query from 100-99 weeks ago and add the data to the chart. Wait 10 seconds and query now instead 99-98 until it gets to zero.
I am pretty new to JS in general so I'm not too sure how to start but I have read about the setTimeout function. Any suggestions or better way to go about this would be much appreciated.
My current http request looks like this and is static:
$http({
url: '/api/v1/datapoints',
method: 'POST',
data: '{"start":"99w-ago","end":"98w-ago","tags":[{"name":"SolarData"}]}'
}).then(function(predixTimeSeriesData){
$scope.solarData = predixTimeSeriesData.data.tags[0].results[0].values.map(
function(curVal, index, arr) {
return [curVal[0], curVal[1]];
}
);
console.log($scope.solarData);
/*
I use $scope.solatData in my chart on the html page like
<line-series-chart data={{solarData}}></line-series-chart>
so this is why I am thinking I need to have the time interval on the view page
instead of the controller because i cannot control my chart from there
*/
});
You can use the $interval service of angular, something like this:
function myController($scope, $http, $interval) {
var currentWeek = 99;
var fetchInterval;
$scope.solatData = [];
$scope.fetch = function() {
$http.get("someUrl", {
params: {
week: currentWeek
}
}).then(function(data){
// This will also update your graph, assuming it is implemented
// to watch changes on the data
$scope.solatData = $scope.solatData.concat(data);
currentWeek++;
});
}
$scope.start = function() {
fetchInterval = $interval($scope.fetch, 10000);
}
// Clear the interval when the scope/controller is 'destroyed'
$scope.$on('$destroy', function() {
$interval.cancel(fetchInterval);
});
// kick off initial start
$scope.start();
}
I have a object in mainController.js that is set as default as 99.
I am obtaining user location and do running some other function with it to calculate this value.
However, When I load the page, the page seems to load faster than this process. Therefore it displays 99 instead of the calculated value.
If I put console.log after the calculation, the object is successfully changed.
edit1:
status.success( function(data)
{
$scope.current = data;
$scope.$broadcast('back_end_connected');
});
$scope.getLocation = function()
{
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
var dirBuilt = false;
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
}
};
});
that "alert("aaa: "+ $scope.facilities[0].distance);" returns the value I want it to display but it is not displayed on the page....
(ng-bind would not work for some reason...)
How can I make the html wait for the operation? Thanks
You should not make HTML wait until you finish your Js code ! What you should be doing is showing a placeholder value - Loading image so that user know the page is loading some data.
Once you are done with your calculation, hide /replace the loading image with the data you want to show.
Quick example
Your view markup will have some HTML element to show the progress bar.And all your other contents will be in another div
<body ng-app="yourApp" ng-controller="yourCtrl as vm">
<div ng-show="loaderCount>0"> Loading something..</div>
<div ng-show="loaderCount==0">
<h4>{{userScore}}</h4>
</div>
</body>
And in your angular controller, You have a scope variable called loaderCount which you will increase everytime when you are doing some operation (http call/Long running function execution etc..). When you get your result, You decrease this variable value back. In your View You are hiding and showing the Loading Pane based on this value.
var yourApp= angular.module('yourApp', []);
var ctrl = function($scope, $http) {
var vm = this;
vm.loaderCount = 0;
vm.someValue = "";
vm.actionItems = [{ Name: "Test" }, { Name: "Test2" }];
vm.loaderCount++;
$http.get("../Home/GetSlowData").then(function(s) {
vm.loaderCount--;
vm.someValue = s.data;
});
};
yourApp.controller('yourCtrl', ['$scope', '$http', ctrl]);
This might not be the best angular code. But this will give you an idea about how to handle this use case. You should be using services to talk to Http endpoints instead of directly using $http in your angular controller.
Note that, $http.get returns a promise which allows you do things when you get the response from this asynchronous operation (the then event). You should make sure that your time taking calculation is returning a promise.
You can bind with the ngBind directive instead of {{}} and not write to the binded property when the calculation is done.
This will hide the result from the view while it is null.
Try to do something like this:
$scope.value = "Loading";
or
$scope.value = "";
$scope.calculate = function() {
$scope.value = yourcalculation;
}
$scope.calculate();
Or in your case i think if you use $scope.$apply() after you add data to your scope element will do the trick
Try this :
$scope.$on('location_obtained', function(){
$scope.buildDist = function()
{
if(dirBuilt === false)
{
$scope.facilities[0].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[0].location.lat,$scope.facilities[0].location.lng);
$scope.facilities[1].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[1].location.lat,$scope.facilities[1].location.lng);
$scope.facilities[2].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[2].location.lat,$scope.facilities[2].location.lng);
$scope.facilities[3].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[3].location.lat,$scope.facilities[3].location.lng);
$scope.facilities[4].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[4].location.lat,$scope.facilities[4].location.lng);
$scope.facilities[5].distance = distCalc($scope.location.lat,$scope.location.lng,$scope.facilities[5].location.lat,$scope.facilities[5].location.lng);
$scope.$broadcast('dist_obtained');
dirBuilt = true;
alert("aaa: "+ $scope.facilities[0].distance);
$scope.$apply(); // This should do the trick
}
};
});
Are you using a promise on the service? The function running your calculation can be in a separate angular factory which has .success and .error events to tap into. If the calculation is a success. You can pass the data back to your controller and then bind that data to the controller scope. I'll be at a computer soon and will add some code to explain further if needed.
This would be your distance calculator, I used a Factory over a service but you can read about why to use one over the other
A couple of things to keep in mind. You geolcation is more of a service then something to control since you're requesting from the user, you could add it to the below factory to expand the factories capabilities and allow you to use getLocation in other controllers.
make sure you add the distance service to your html document
also make sure you put the distance service in your controller and on the main angular app.
distanceService.js
(function(){
'use strict'
var distSrvc= angular.module("distanceService",[])
distSrvc.factory('DistanceCalc',['$http',function($http){
return{
getDistance:function(facilitiesData,locationData){
Object.keys(facilitiesData).length; // or if it's already an array of facilities use facilitiesData.length
// if its a javascript object and distance is already defined
for (var distance in facilitiesData) {
if (facilitiesData.hasOwnProperty(distance)) {
facilitiesData.distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
}
// if its an array of objects
for (var i = 0 ; i< facilitiesData.length;i++){
facilitiesData[i].distance = distCalc(locationData.lat,locationData.lng,facilitieData.location.lat,facilitieData.location.lng);
}
return facilitiesData
}
})();
Then in your controller you'll need to load the service for use.
yourcontroller.js this will give errors if you don't load it on the html page and add it to the main angular app.
(function(){
'use strict'
var myController= angular.module("distanceService",[])
myController.controller('myController',['$scope','DistanceCalc',function($http,DistanceCalc){ // see how i added the service and passed it to the function
$scope.getLocation = function(){
if (navigator.geolocation)
{
navigator.geolocation.getCurrentPosition(function (position){
$scope.location = {lat: position.coords.latitude, lng: position.coords.longitude};
$scope.$broadcast('location_obtained');
$scope.buildDist();
$scope.fetch();
//$scope.getRec();
});
}
else{
alert("Geolocation is not supported by this browser.");
}
};
// this line below sends the data to the service
DistanceCalc.getDistance($scope.facilities,$scope.location)
.success(function(data){
//success i'm done running distance calculations and now i want to update the facilties object with the added distance
$scope.facilities = data
})
.error(function(data){
// some error occurred and data can tell you about that error
})
}
})();