How to programmatically fill values in successive select in AngularJS? - angularjs

Programmatically
So I am facing an issue with the select in Angular JS. This is a follow-up question to my previous question
Now I am able to load my values in the second select but I am unable to write the same value to it when the query from DB comes back. So If I select Ford in one select and Figo in another. and press save. The values go into DB correctly but when I come again to that view. Shouldn't that value persist right? I mean I should be able to see figo in the second select. But I am unable to. I tried setting to the ng-model but that method doesn't work. I have also attached a fiddle with the proper comments as to what is not working.
The code uses one of the marked answers from the question.
HTML
<select ng-model="carBrand" name="carBrand" required ng-options=" brand for brand in brands" ng-change="selectedCar(carBrand)"></select>
<select ng-model="carModel" name="carModel" required ng-options="model.name for model in cars[carIndex]"></select>
JS
$scope.brands = ['Ford', 'Honda', 'Hyundai', 'Mahindra',
'Maruti Suzuki', 'Nissan', 'Renault', 'Skoda', 'Tata', 'Toyota', 'Volksvagen'
];
$scope.cars[0] = $scope.cars[0] = [{
name: "Figo",
capacity: 45
}, {
name: "Ecosport",
capacity: 52
}, {
name: "Fiesta",
capacity: 45
}, {
name: "Endeavour",
capacity: 71
}];
$scope.carBrand = $scope.brands[0];
$scope.cars = [];
$scope.selectedCar = function(brand) {
$scope.carIndex = $scope.brands.indexOf(brand);
};
$scope.carModel = "Anything";
EDIT
I think there is an issue in the understanding of the question. All I want is programmatically(by code) set the value of the second select. That's it.

I have forked your fiddle. Have a look: https://jsfiddle.net/4ynkrj0o/2/
var MockSaveMyData = function(itemsToSave){
// I JUST SAVED ALL MY DATE !! :D
var savedItems = itemsToSave;
savedItems.CreatedAt = new Date();
savedItems.CreatedBy = 'YOU !!';
return {
then: function(callback){
callback(savedItems);
}
};
};
You will have to save and retrieve the values on every page refresh.
Like you said, you have cookies where you save your Car object. In that case, you will have to assign the object on the top firth thing inside the controller.
You will also have to update the cookies after you have run the save function. I will suggest doing all this in the success call back which http calls provide. You can update the cookie and then refresh the page. Or you can just assign the updated values and give a message to the user stating that the save call has been successful.
Again, in my opinion, you should avoid reloading the page. It almost defies the essence of Angular. Where you can do almost everything asynchronously.

Related

Angular Select not updating if model changes

I am working on a website which uses Angular (1.6.4) Select. The content for the select element is loaded via REST if it is requested the first time. But the response of the REST call is cached so that following calls load the data from memory. If I load the website the select element is empty and I can't select any value. If I visit the site again with the data cached, the selectbox allows you to select items from a list. If I redirect the REST call to a file containing the data it works on the first attempt and I can select items as expected
So it seems that the code works in principle but if the model is updated too late the select element does not notice the changes.
Here is the code I am using:
<select ng-model="myData" chosen
class="select--chosen"
ng-change="handleSelection()"
ng-options="myData as myData.value for myData in dataArray">
</select>
The controller code looks like this (called when site is opened):
$scope.dataArray = [];
//$scope.dataArray = [{value : "just a test value"}];
$scope.$watch('dataArray ', function() {console.log("dataArray was changed...")}, true);
getArray();
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.dataArray = data;
});
}
I do get the watch message when I load the site for the first time. When looking for a solution I found several hints but none of them worked for me. This is what I tried:
1) Add
$scope.$apply(function(){ /* code */ });
to set the dataArray inside this function or call it inside of the watch-function. In both cases I got the error on the console that the digest is already updating or so, indicating that it is not neccessary to use scope.$apply
2) Use
$scope.onChange($scope.dataArray);
after setting dataArray = data.
Unfortunately nothing worked. If I uncomment the line:
$scope.dataArray = [{value : "just a test value"}];
I can choose this entry after loading the page and the select view then shows the first entry of the dataArray and afterwards I can access the whole list and select items from it.
So I would like to know what I can do to update the select view after the data is available. Either by adding a Listener or by manually calling the select view to update(), refesh() or so. Is there such a function available?
You can show your select element by some boolean flag, which sets true, when
data loaded.
You can do something like below code.
In controller :
$scope.dataArray = [];
$scope.myData= null;
$scope.isDataLoaded = false; //flag for data loading.
function getArray() {
DataFactory.getArray().then(function (data) {
$scope.isDataLoaded = true; // make true now
$scope.dataArray = data.data; //your result might be data.data
$scope.myData = $scope.dataArray[0]; // you may select 1st as default
});
}
getArray();
In html:
<select ng-if="isDataLoaded" ng-model="myData" ng-class="select-chosen"
ng-options="myData as myData.value for myData in dataArray">
</select>

Angular array dupe error when adding new objects to an array

I have an alert system, and the user needs to be able to create an unlimited number of alerts and add an unlimited number of triggers to each alert.
For instance the user may have an alert called "When prices change on my cars". They need to be able to create a trigger of the same type ("Price Change") for each of the cars they want to follow.
What follows is a stripped-down version, only dealing with the triggers.
Here's a plunker - just press Add twice and you'll see the issues.
JS
// the array of trigger possibilities
$scope.triggerOptions = [
{id : 0, type: 1, action: "Status Update For Brand"},
{id : 1, type: 2, action: "Price Changed for "}
];
// the model for the select element
$scope.selected = $scope.triggerOptions[0];
// this array will hold all the triggers created
$scope.triggers = [];
// add some indexes to the new trigger object, then add it to triggers
$scope.addTrigger = function() {
var newTrigger = $scope.selected;
var newID = $scope.triggers.length;
var alertID = 0; // todo: apply alert id
newTrigger.triggerID = newID;
newTrigger.alertID = alertID;
$scope.triggers.push(newTrigger);
};
HTML
<select ng-options = "option.action for option in triggerOptions track by option.id" ng-model = "selected"></select>
<button ng-click="addTrigger()">Add</button>
<div ng-repeat="trigger in triggers track by triggerID" class="alert-tool-action-box">
<div ng-show="trigger.type==1">
<div>{{trigger.action}}</div>
</div>
<div ng-show="trigger.type==2">
<div>{{trigger.action}}</div>
</div>
</div>
Issues
When I add more than one trigger, only the first trigger is shown, but I get a "dupes" error message (which suggests I add a 'track by', but I already did).
When I add two triggers of the same type in a row, the triggerID is updated to the new triggerID for both triggers:
one trigger:
[{"id":0,"type":1,"action":"Status Update For Brand","triggerID":0,"alertID":0}]
two triggers:
[
{"id":0,"type":1,"action":"Status Update For Brand","triggerID":1,"alertID":0},
{"id":0,"type":1,"action":"Status Update For Brand","triggerID":1,"alertID":0}
]
I should be able to see each trigger as I add them, even if they're the same as the one before.
Your array of objects can't contain duplicates (Error: [ngRepeat:dupes]), now these two objects are equal. This can be solved using track by, which must be unique, so triggerID can't be used here. You can always use track by $index (provided by ng-repeat directive) if no unique property is available.
About your second problem, you are always pushing the same object, as you only have 1 instance of the $scope.
To solve that, you just need to clone (or copy) the $scope.selected, like this:
var newTrigger = angular.copy($scope.selected);
Hope it helps!
You ng-repeat show only one representant of you array because you track by triggerID which don't exist (angular should search it on the $scope and return undefined each time it call it). The good way to call triggerID will be trigger.triggerID. So :
<div ng-repeat="trigger in triggers" class="alert-tool-action-box">
....
</div>
or if you want to use track by :
<div ng-repeat="trigger in triggers track by trigger.triggerID class="alert-tool-action-box">
....
</div>
Your second issue is linked to the fact that javascript pass object by reference and not by value. You don't create a new object. You just pass the same and change its value. It's why you have all your object that are updated with the same id.
So you can use angular.copy() to make it different object. Something like :
$scope.addTrigger = function() {
var newTrigger = angular.copy($scope.selected);
var newID = $scope.triggers.length;
var alertID = 0; // todo: apply alert id
newTrigger.triggerID = newID;
newTrigger.alertID = alertID;
$scope.triggers.push(newTrigger);
};

How to set the key name on an array in Angularfire

I am creating arrays using Angularfire but I am unable to set the key name. Firebase is automatically giving me a cryptic key (eg Jm9vLy8Lye-KS35KsmL) but I would like to set it myself to something more meaningful. It is unclear how I do this in Angularfire. I am using the $add method on $firebaseArray:
var firebaseRef = new Firebase("https://firebase_location");
$scope.messages = $firebaseArray(firebaseRef);
$scope.messages.$add(
{
FirstName: patient.FirstName,
LastName: patient.LastName
}
).then(function(firebaseRef) {
var id = firebaseRef.key();
});
The data is stored fine and I can see it on my dashboard. However, id is always a cryptic firebase value. I would love to be able to set it myself to something meaningful. In my case the individual patient's ID would be meaningful...
Any thoughts?
Thank you!
Thanks for the response.
I found that the child function will do what I need. I first specify the child and then set it.
var patient_child = firebaseScreenRef.child(patient.PatientId);
patient_child.set(
{
FirstName: patient.FirstName,
LastName: patient.LastName,
});
Adding an item to a firebase array make firebase define a unique id for you. If this is not what you want, I think you can try getting the "messages" object with $firebaseObject, instead of getting just the list with $firebaseArray. Then you will be able to edit your object in js, in order to add your items to the messages collection. In this way you can use the id that best suits your needs. Finally, you have to $save() your entire object. Look here: https://www.firebase.com/docs/web/libraries/angular/api.html#angularfire-firebaseobject
Hope this helps.
For your question, I found an explanation. You don't need to use firebaseObject, you should use ref directly:
var ref = new Firebase(FURL);
createProfile: function(id ,user) {
var profile = {`enter code here`
name: user.name,
email: user.email,
gravatar: get_gravatar(user.email, 40)
};
var profileRef = $firebaseArray(ref.child('profile').child(id));
return ref.child('profile').child(id).set(profile);
},
In the code, I use ref to reference my URL. With profileRef, I created a child profile and I added id for the profile. Afterwards, I use ref directly to set the value profile for the id that I want. You see, it is very easy.

Setting default <select> value with asynchronous data

I have a drop-down list
<select ng-model="referral.organization"
ng-options="key as value for (key, value) in organizations">
</select>
where organizations is filled using a $http request. I also have a resource referral which includes several properties, including an integer organization that corresponds to the value saved in the drop-down. Currently, the drop-down works fine and selecting a new value will update my referral resource without issue.
However, when the page loads the drop-down is blank rather than displaying the value of referral.organization that was retrieved from the server (that is, when opening a previously saved referral form). I understand that this is likely due to my resource being empty when the page first loads, but how do I update the drop-down when the information has been successfully retrieved?
Edit:
{{ organizations[referral.organization] }} successfully lists the selected value if placed somewhere else on the page, but I do not know how to give the tag this expression to display.
Second Edit:
The problem was apparently a mismatch between the key used in ngOptions and the variable used in ngModel. The <select> option's were being returned as strings from WebAPI (despite beginning as Dictionary) while the referral model was returning integers. When referral.organization was placed in ngModel, Angular was not matching 2723 to "2723" and so forth.
I tried several different things, but the following works well and is the "cleanest" to me. In the callback for the $resource GET, I simply change the necessary variables to strings like so:
$scope.referral = $resource("some/resource").get(function (data) {
data.organization = String(data.organization);
...
});
Not sure if this would be considered a problem with Angular (not matching 1000 to "1000") or WebAPI (serializing Dictionary<int,String> to { "key":"value" }) but this is functional and the <select> tag is still bound to the referral resource.
For simple types you can just set $scope.referral.organization and it'll magically work:
http://jsfiddle.net/qBJK9/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c for c in organizations">
</select>
</div>
-
function MyCtrl($scope) {
$scope.organizations = ['a', 'b', 'c', 'd', 'e'];
$scope.referral = {
organization: 'c'
};
}
If you're using objects, it gets trickier since Angular doesn't seem smart enough to know the new object is virtually the same. Unless there's some Angular hack, the only way I see forward is to update $scope.referral.organization after it gets loaded from the server and assign it to a value from $scope.organizations like:
http://jsfiddle.net/qBJK9/2/
<div ng-app ng-controller="MyCtrl">
<select ng-model="referral.organization" ng-options="c.name for c in organizations"></select>
{{referral}}
<button ng-click="loadReferral()">load referral</button>
</div>
-
function MyCtrl($scope) {
$scope.organizations = [{name:'a'}, {name:'b'}, {name:'c'}, {name:'d'}, {name:'e'}];
$scope.referral = {
organization: $scope.organizations[2]
};
$scope.loadReferral = function () {
$scope.referral = {
other: 'parts',
of: 'referral',
organization: {name:'b'}
};
// look up the correct value
angular.forEach($scope.organizations, function(value, key) {
if ($scope.organizations[key].name === value.name) {
$scope.referral.organization = $scope.organizations[key];
return false;
}
});
};
}
You can assign referral.organization to one of objects obtained from ajax:
$scope.referral.organization = $scope.organizations[0]
I created simple demo in plunker. Button click handler changes list of objects and selects default one.
$scope.changeModel = function() {
$scope.listOfObjects = [{id: 4, name: "miss1"},
{id: 5, name: "miss2"},
{id: 6, name: "miss3"}];
$scope.selectedItem = $scope.listOfObjects[1];
};
The other answers were correct in that it usually "just works." The issue was ultimately a mismatch between the organization key (an integer) stored inside $scope.organizations and the key as stored in the $http response, which is stored in JSON and therefore as a string. Angular was not matching the string to the integer. As I mentioned in my edit to the original question, the solution at the time was to cast the $http response data to a string. I am not sure if current versions of Angular.js still behave this way.

Angularjs autoselect dropdowns from model

I'm trying display some data loaded from a datastore and it's not reflecting changes on the UI. I created an example to show a general idea of what I'm trying to achieve.
http://plnkr.co/edit/MBHo88
Here is the link to angularjs example where they show when on click then dropdowns are clear out. If you replace the expression with one of the colors of the list dropdowns are well selected. Does this type of selection only work on user events?
http://docs.angularjs.org/api/ng.directive:select
Help is appreciated!!!
Actually the problem is that ngSelect compares objects using simple comparition operator ('=='), so two objects with same fields and values are considered as different objects.
So you better use strings and numbers as values ('select' parameter in expression of ngSelect directive).
Here is kind of solution for your plunker.
Aslo there are some discussion about this topic on GitHub:
https://github.com/angular/angular.js/issues/1302
https://github.com/angular/angular.js/issues/1032
Also as I headred there is some work in progress about adding custom comparor/hashing for ngSelect to be able to use ngSelect more easier on objects.
One mistake in the initialization of your controller. You have to refer to the objects in your palette, since these are watched on the view:
$scope.selectedColors.push({col: $scope.palette[2]});
$scope.selectedColors.push({col: $scope.palette[1]});
Same with your result:
$scope.result = { color: $scope.obj.codes[2] };
Then you need to watch the result. In the below example, select 1 receives the value from the initiating select, the second receives the value below in the palette. I don't know if that's what you wanted, but you can easily change it:
$scope.$watch('result', function(value) {
if(value) {
var index = value.color.code -1;
$scope.selectedColors[0] = {col: $scope.palette[index] };
$scope.selectedColors[1] = {col: $scope.palette[Math.max(index-1, 0)] };
}
}, true);
See plunkr.
Ok, I think I figured this out but thanks to #ValentynShybanov and #asgoth.
According to angularjs example ngModel is initialized with one of the objects from the array utilized in the dropdown population. So having an array as:
$scope.locations = [{ state: 'FL', city: 'Tampa'}, {state: 'FL', city: 'Sarasota'} ....];
And the dropdown is defined as:
<select ng-options="l.state group by l.city for l in locations" ng-model="city" required></select>
Then $scope.city is initialized as:
$scope.city = $scope.locations[0];
So far so good, right?!!!.. But I have multiple locations therefore multiple dropdowns. Also users can add/remove more. Like creating a table dynamically. And also, I needed to load data from the datastore.
Although I was building and assigning a similar value (e.g: Values from data store --> State = FL, City = Tampa; Therefore --> { state : 'FL', city : 'Tampa' }), angularjs wasn't able to match the value. I tried diff ways, like just assigning { city : 'Tampa' } or 'Tampa' or and or and or...
So what I did.. and I know is sort of nasty but works so far.. is to write a lookup function to return the value from $scope.locations. Thus I have:
$scope.lookupLocation = function(state, city){
for(var k = 0; k < $scope.locations.length; k++){
if($scope.locations[k].state == state && $scope.locations[k].city == city)
return $scope.locations[k];
}
return $scope.locations[0]; //-- default value if none matched
}
so, when I load the data from the datastore (data in json format) I call the lookupLocation function like:
$scope.city = $scope.lookupLocation(results[indexFromSomeLoop].location.state, results[indexFromSomeLoop].location.city);
And that preselects my values when loading data. This is what worked for me.
Thanks

Resources