I am working on a search suggestion functionality using firebase and angular js. Basically on key up of a search input box, I call the below function:
scope.suggestString = {};
scope.search = function(){
scope.suggestString = {};
firebaseDb.ref("users")
.orderByChild("Name")
.startAt(scope.searchedString)
.endAt(scope.searchedString + "\uf8ff")
.on("value", function(snapshot) {
snapshot.forEach(function(childSnapshot) {
if(scope.suggestString.hasOwnProperty(childSnapshot.key) == false){
scope.suggestString[childSnapshot.key] = childSnapshot;
}
});
});
}
HTML:
<form>
<input type="text" ng-model="searchedString" ng-keyup="search()">
<ul>
<li ng-repeat="(key,item) in suggestString">
{{item.val().firstName}}
</li>
</ul>
</form>
The code works , the call goes to firebase and fetches the records but I have to click somewhere in search input box for the suggestions to be displayed.
I tried using scope.$apply() but its not working .Its says already applying scope
maybe you can use something like:
<input ng-model="searchedString" ng-model-options="{debounce: 1000}" type="text">
on input box, which will update ng-model (searchString) with search string in input element, after 1 second delay of typing.
After that you can put something like:
scope.$watch('searchedString', (newVal,oldVal)=>{
scope.suggestString = {};
if(scope.searchedString.length <3){
return;
}
firebaseDb.ref("users")
.orderByChild("Name")
.startAt(scope.searchedString)
.endAt(scope.searchedString + "\uf8ff")
.on("value", function(snapshot) {
snapshot.forEach(function(childSnapshot) {
if(scope.suggestString.hasOwnProperty(childSnapshot.key) == false){
scope.suggestString[childSnapshot.key] = childSnapshot;
}
});
});
});
Now it should work if your code for getting data is correct.
Angular doesn't know about this async call, so you have to trigger a digest. You can do that safely without getting a console error by calling $evalAsync after you get the response:
// ...
.on("value", function (snapshot) {
scope.$evalAsync(function () {
snapshot.forEach(function (childSnapshot) {
if (scope.suggestString.hasOwnProperty(childSnapshot.key) == false) {
scope.suggestString[childSnapshot.key] = childSnapshot;
}
})
});
});
But I suspect scope.$apply will be fine too, if you do it outside the loop as I am doing above.
Another option is to wrap all of this firebase functionality into a separate function (possibly in a service), and return a promise from that function using the $q service, and resolve the firebase response. Then you won't have to manually trigger a digest, since Angular knows about the $q service.
Safe apply was the thing I was looking for, the search is smooth now.
Related
I am stuck on getting value back after I call a function in my HTML file. I did some research and tried could things but no success. Please see below code:
HTML:
<div class="form-group">
<div class="col-sm-9"><label for="IPAddr">IP Address</label><input ng-model="IPAddr" id="IPAddr" name="IPAddr" ng-blur="IPAvail=validateIP()"
type="text" class="form-control">{{IPAvail}}</div>
</div>
A basic form with an input field for IP address. Calls the validateIP(), and shows returned value "{{IPAvail}}".
JS:
$scope.validateIP = function(IPAvail) {
$http.defaults.headers.post["Content-Type"] = "application/x-www-form-urlencoded";
var ipVar = $.param({ip: $scope.ipadd});
$http({
url: 'https://test.com/flask_server/findIP',
method: "POST",
data: ipVar
})
.then(function(response) {
$scope.result = response.data;
ip = response.data;
if (ip.indexOf("10.10.10.1") >=0) {
alert("Matches with IP")
IPAvail = "Yes"
return IPAvail
} else {
alert("Does not match with IP")
}
IPAvail = "No"
return IPAvail
});
};
I simplified the code to make it easy to read but basically, everything is working correctly but the only thing I am having an issue is returning IPAvail variable back to HTML so it can be used here:
type="text" class="form-control">{{IPAvail}}</div>
Thanks
Damon
Instead of returning IPAvail, set $scope.IPAvail to the required value. Rather than use onblur, you should defer to ng-change="validateIP()".
Using ng-change allows AngularJS to know about the effect. Otherwise you may run into trouble with AngularJS not knowing. It is better for AngularJS to know.
I have an ng-repeat which creates a form with some starting data. Then the user is free to modify said data and the changes should appear in the form. Before that, the user submitted data are sanitized by another function, which is called by an ng-click on a button.
Everything works well under the hood (I checked my $scope.some_array, from which ng-repeat takes the data and the new data is in the right place) but nothing happens on the page.
The element:
<li ng-repeat="field in some_array" id="field-{{$index}}">
<div class="{{field.field_color}}">
<button type="button" ng-click="save_field($index)">done</button>
{{field.nice_name}}
</div>
<div id="field-input-{{$index}}">
<input type="text" id="{{field.tag}}" value="{{field.content}}">
<label for="{{field.tag}}">{{field.nice_name}}</label>
</div>
</li>
save_field code:
$scope.save_field = function (index) {
console.log($scope.some_array[index]["content"])
var value = $("#field-" + index).children("div").children("input").val()
var name = $scope.some_array[index]["name"]
var clean_value = my_clean(value)
if (norm_value === "") {
return
}
$scope.some_array[index]["content"] = clean_value
console.log($scope.some_array[index]["content"])
}
On the console I see:
10.03.16
10/03/16
Which is right, but in the form I only see 10.03.16. I already tried putting $timeout(function(){$scope.$apply()}) as the last line of my function, but the output is still the same.
You shouldn't use input like this if you want to bind a variable to it. Digest loop will refresh the value but it will not be updated visibly because this is not html native behavior.
Use ng-model instead, it will update view value of the input as expected:
<input type="text" id="{{field.tag}}" ng-model="field.content">
Also using ng-model your variable will be updated when user modify the input, so you can retrieve it to do some treatments much more easily in save_field function, without jQuery:
$scope.save_field = function (index) {
if (norm_value === "") {
return;
}
$scope.some_array[index]["content"] = my_clean($scope.some_array[index]["content"]);
};
More infos: https://docs.angularjs.org/api/ng/directive/ngModel
I am trying to update an json object value from a textbox using angular and I'm not sure what the best way to go about it is.
This is the json object...
$scope.productAttributes = {
"CostRequirements":[
{
"OriginPostcode": 'NW1BT',
"BearerSize":100
}
]
}
And when a use types in a text field and clicks a button, I would like to grab that textfield value and pass it into the json object to replace the postcose value (OriginPostcode) I tried to pass in a scope variable but that didnt work.
<input type="text" placeholder="Please enter postcode" class="form-control" ng-model="sitePostcode"/>
And this is the fucntion that is fired when the user clicks a button to submit the json
var loadPrices = function () {
productsServices.getPrices1($scope.productAttributes)
.then(function (res) {
$scope.selectedProductPrices = res.data.Products;
// $scope.selectedProductAddOns = res.data.product_addons;
})
.finally(function () {
$scope.loadingPrices = false;
$scope.loadedPrices = true;
});
};
Could anyone tell me what I need to do to put the user input in the textbox into the json object?
Many thanks
What we don't see is the function that runs the update with the button. It should look something like this
// your HTML button
<button ng-click='updateThingy()'>Update</button>
// your HTML input
<input type="text" ng-model="myObject.sitePostcode"/>
// your controller
$scope.myObject = { // ties to the ng-model, you want to tie to a property of an object rather than just a scope property
sitePostcode : $scope.productAttributes.CostRequirements[0].OriginPostcode // load in post code from productAttributes
};
$scope.updateThingy = function(){
$scope.productAttributes.CostRequirements[0].OriginPostcode = $scope.myObject.sitePostcode;
};
Here is a demo plunker for updating a value on button click, hope it helps out.
http://plnkr.co/edit/8PsVgWbr2hMvgx8xEMR1?p=preview
I guess loadPrices function is inside your controller. Well, then you should have sitePostCode variable available inside your controller and your function. So you just need to inject that value inside $scope.productAttributes.
$scope.productAttributes.sitePostCode = $scope.sitePostCode;
This you need to put it before you make the productsServices.getPrices1 call.
var loadPrices = function() {
$scope.productAttributes.sitePostCode = $scope.sitePostCode;
productsServices.getPrices1($scope.productAttributes)
.then(function(res) {
$scope.selectedProductPrices = res.data.Products;
// $scope.selectedProductAddOns = res.data.product_addons;
})
.finally(function() {
$scope.loadingPrices = false;
$scope.loadedPrices = true;
});
};
Let me know if it worked.
I'm currently working on Ajax and jQuery live search which finds a results in a JSON file. Script is working fine, but the is only one problem - it's duplicating the result data.
EXAMPLE:
MARKUP:
<div class="row">
<h3>Live Search Results</h3>
<div id="update-results">
<p>event_name | club_name | memberid</p>
<ul id="update">
<!-- <li></li> -->
</ul>
</div>
</div>
SCRIPT:
$('#search').keyup(function() {
var searchField = $('#search').val();
var $update = $('#update');
$update.empty();
$.get("getEventsWithVideos.php?text=" + searchField, function(data) {
var vals = jQuery.parseJSON(data);
if($.isArray(vals['Event'])) {
$.each(vals['Event'], function(k,v){
$update.append("<li value='"+v['id']+"'><a href='#'>" + v['event_name'] + "</a></li>");
});
} else {
$update.append("<li value='"+vals['Event']['id']+"'><a href='#'>" + vals['Event']['event_name'] + "</a></li>");
}
});
});
I've tried to debug and stop the error, but it was unsuccessful. Can anyone help me please with that?
Put the empty() inside the response handler:
$.get("getEventsWithVideos.php?text=" + searchField, function(data) {
$update.empty();
basically you are clearing the list on every keystroke (rapid), then requesting the data, then (sometime later) appending the results that come back (which could be multiple results depending on the timing).
I didn't reproduce your error but I suspect that you have problem with multiple request to server and adding them all instead of last one. Probably adding below code will fix your problem
$update.empty();
Anyway I suggest you to use 2 more functions: throtlle and debounce from underscore to prevent too much request on every keyup.
Also you could try Rx.js witch give following example (https://github.com/Reactive-Extensions/RxJS):
var $input = $('#input'),
$results = $('#results');
/* Only get the value from each key up */
var keyups = Rx.Observable.fromEvent($input, 'keyup')
.map(function (e) {
return e.target.value;
})
.filter(function (text) {
return text.length > 2;
});
/* Now debounce the input for 500ms */
var debounced = keyups
.debounce(500 /* ms */);
/* Now get only distinct values, so we eliminate the arrows and other control characters */
var distinct = debounced
.distinctUntilChanged();
Try changing this line $update.empty(); of your code to $update.find('li').remove(); and put it inside the response handler.
This removes all the previous data before you append the new values. Hopefully it might work.
Edit:
Plunker is working, actual code isn't:
http://plnkr.co/edit/5oVWGCVeuTwTARhZDVMl?p=preview
The service is contains typical getter\setter stuff, beside that, it functions fine, so I didn't post it's code to avoid TLDR.
TLDR version: trying to ng-init a value fetched with AJAX into the ngModel of the text-area, the request resolves with the correct value, but the textarea remain empty.
parent controller function(talks to the service):
$scope.model.getRandomStatus = function(){
var deffered = $q.defer();
var cid = authService.getCompanyId();
var suggestions = companyService.getStatusSuggestions(cid);
if(suggestions && suggestions.length > 0){
deffered.resolve(suggestions[Math.floor(Math.random(suggestions.length) + 1)].message);
return deffered.promise;//we already have a status text, great!
}
//no status, we'll have to load the status choices from the API
companyService.loadStatusSuggestions(cid).then(function(data){
companyService.setStatusSuggestions(cid, data.data);
var result = data.data[Math.floor(Math.random(data.data.length) + 1)];
deffered.resolve(result.message);
},
function(data){
_root.inProgress = false;
deffered.resolve('');
//failed to fetch suggestions, will try again the next time the compnay data is reuqired
});
return deffered.promise;
}
child controller:
.controller('shareCtrl', function($scope){
$scope.layout.toggleStatusSuggestion = function(){
$scope.model.getRandomStatus().then(function(data){
console.log(data);//logs out the correct text
//$scope.model.formData.shareStatus = data;//also tried this, no luck
return data.message;
});
$scope.model.formData.shareStatus = $scope.layout.toggleStatusSuggestion();//Newly edited
}
});
HTML:
<div class="shareContainer" data-ng-controller="shareCtrl">
<textarea class="textAreaExtend" name="shareStatus" data-ng-model="model.formData.shareStatus" data-ng-init="model.formData.shareStatus = layout.toggleStatusSuggestion()" cols="4"></textarea>
</div>
I believe what you are wanting is :
$scope.model.getRandomStatus().then(function(data){
$scope.model.formData.shareStatus = data.message;
});
Returning something from within then does not return anything from the function wrapping it and therefore does nothing
Turns out that I had a custom validation directive that was watching the changes in the model via $formatters, and limting it to 80 chars(twitter), it was failing silently as I didn't expect to progmatically insert invalid values into my forms, very stupid, but could happen to anyone.
Had to make some changes to it, so it's worth to remember in case it happens to anyone else.