AngularJS adding 2 properties to associated collections - angularjs

1. I have a Truck class that has a collection called AxleTypes with the following markup:
public class AxleType : Entity
{
public string Description { get; set; }
public string Type { get; set; }
}
2. My angular form includes the following (to keep this as short as possible I omitted the form's other axle types, and I am using $parent, as this is a template and on main form thru ng-include):
<label for="frontAxles">Front Axle: </label>
<input ng-model="$parent.frontAxleType" type="hidden" value="Front">
<select ng-model="$parent.selectedFrontAxle" ng-options="axleType.Description for axleType in axleTypes">
<option value="" selected>Select Axle Type ..</option>
</select>
3. The main form inserts a new truck via the truck controller, so in the truck controller:
a. I instantiate an instance of the AxleTypes collection so the form is populates the select w/axle types.
b. I instantiate an instance of AxleType to pass the selected data from the form.
c. I pass the respective ng-models on the form to the AxleType variable.
d. I add that AxleType variable to the Truck's AxleTypes collection.
a: if ($scope.axleTypes == undefined || !($scope.axleTypes.length > 0))
{ $scope.axleTypes = API.GetAxleTypes(); }
b: $scope.axleType = {};
c: var frontAxle = $scope.axleType;
frontAxle.Description = $scope.selectedFrontAxle;
frontAxle.Type = $scope.frontAxleType;
d: newTruck.AxleTypes = [
angular.copy(frontAxle)
];
When debugging this is the end result:
To keep this as short as possible I did not illustrate the 2nd axle type select above. But as you can see, the server is picking up 2 axle types however, both properties for each [Type & Description] are null.
Does anyone have any suggestions?
In console.log both values are "undefined".
An interesting observation:
When I hard code the values in the TruckCtrl, everything works fine:
frontAxle.Description = 'Some Front Value';
frontAxle.Type = 'Front';
rearAxle.Description = 'Some Rear Value';
rearAxle.Type = 'Rear';
newTruck.AxleTypes = [
angular.copy(frontAxle),
angular.copy(rearAxle)
];
This would lead someone to think the problem lies with the ng-model on the axle template. However, when I only had one property, ie Description, and not Type in the server class, AxleType, and merely added the select's description via:
newTruck.AxleTypes = [
angular.copy($scope.selectedFrontAxle),
angular.copy($scope.selectedRearAxle)
];
The values passed. Very confusing.

SOLVED !!!
There is alot I would like to say to the 40 some odd eyeballs who perused this problem with no input, and Stewie, well, we know what Stewie is. But I won't. Instead I offer help to those who have this same problem.
OK, on to the solution.
Problem #1: Angular does not accept default values in html inputs.
I was confused why I had an input like so:
<input ng-model="$parent.selectedRearType" type="hidden" value="Front">
and yet angular said it had no value.
SOLUTION TO PROBLEM #1:
You need to initialize the following in your controller:
$scope.selectedFrontType = 'Front';
$scope.selectedRearType = 'Rear';
Problem #2: How do you pass values of multiple properties in a collection?
Despite how real-world this scenario is, it is unsettling that there is ZERO documentation on the matter.
SOLUTION TO PROBLEM #2:
In my html I had this select statement:
<select ng-model="$parent.selectedFrontAxle" ng-options="axleType.Description for axleType in axleTypes">
Where I erred was thinking this ng-model was STRICTLY the Description property of the class AxleType (see my class model above). That was a huge mistake, it was not. That ng-model was not the Description property of the class but actually the entire class itself. So when examining that select's ng-model, realize it is the AxleType class in its entirety, even if only Description property is provided. So what ng-model was giving me was this as a return value in my controller after the user made a selection:
AxleType class => Description = "Whatever the person selected", Type=""
With that being the case, I needed to fill in the blanks angular did not have, namely in this scenario, the Type property.
Here's the whole solution:
// beginning of controller when it initializes
$scope.selectedFrontType = 'Front';
$scope.selectedRearType = 'Rear';
// $scope.axleType = {}; -> NOT NEEDED
// in the save method
// axles
var frontAxle = $scope.selectedFrontAxle;
frontAxle.Type = $scope.selectedFrontType;
var rearAxle = $scope.selectedRearAxle;
rearAxle.Type = $scope.selectedRearType;
newTruck.AxleTypes = [
angular.copy(frontAxle),
angular.copy(rearAxle)
];
I hope I have helped someone!

Related

Issue on prepopulating model value on view init in angularJS

I want to prepopulate an input field from my controller:
Here is the input field:
<input class="form-control" type="text" name="partnerName" placeholder="Completeaza numele partenerului" ng-model="partnerNameModel.field" required validate-field="partnerNameModel">
In my controller,
If I do this:
partnerNameModel.field = 'test';
I get the following error:
TypeError: Cannot set property 'field' of undefined
So, I had to do it like this:
$scope.partnerNameModel = {field: 'dsad'};
I this good practice?
Is there a better way to prepopulate fields?
You can create the object partnerNameModel by doing
$scope.partnerNameModel = {}
at the top of your controller then you can use the dot syntax to set values like
$scope.partnerNameModel.value = "foo"
$scope.partnerNameModel.bar = "lemons"
This is how I personally work with objects in Angular
When you are dealing with an input that has a placeholder, it makes sense to put no default value.
However, the object you are using must be created or it will be a big pain in the ass.
I recommend that you simply use:
$scope.partnerNameModel = {};
Make sure to initialize your fields that don't use a non-empty default value (a dropdown as an example).
$scope.partnerNameModel = {
myDrop: $scope.myList[0]
};

Nesting angular binding in ng-repeat

Just curious if this is something that is currently possible in Angular, or if I have to write my own directive.
I'm attempting to dynamically create input tags for each property on any object. To accomplish this, I am inspecting the object in javascript and creating an array that contains the objects property names mapped to the corresponding input "type".
Ex.
var fields = []
for (var name in myObject) {
if (!myObject.hasOwnProperty(name)) continue;
fields.push({ 'name': name, 'inputType': typeToInputType(myObject[name]) });
}
$scope.fields = fields;
Then in my markup I go ahead and do the following:
<label for="{{f.name}}" ng-repeat="f in fields">
{{f.name}}: <input name="{{f.name}}" ng-model="myObject.{{f.name}}" type="{{f.inputType}}" />
</label>
Everything works great except when we get to this attribute:
ng-model="myObject.{{f.name}}"
That is causing serious problems. The idea behind it is that I have a
$scope.myObject
and when people type stuff into the inputs, for example the "Name" input, then it should be equivalent to:
$scope.myObject.Name = "I typed stuff";

Angular select adding empty element despite carefulness

I have this html:
<select ng-model="group.newCriterionType">
<option ng-repeat="type in group.availableTypes" value="{{type}}" ng-selected="$first">{{type}}</option>
</select>
And this model code (using jresig's Class mini-library)
var FilterGroup = Class.extend({
className: 'FilterGroup',
widgetType: 'group',
init: function(name) {
this.name = name;
this.availableTypes = FilterTypes;
this.newCriterionType = this.availableTypes[0];
this.updateAvailableTypes();
},
});
However, I still get an empty element in my select. According to all the answers I've seen on SO so far, this is apparently the behaviour of when your model value contains an incorrect value. However:
I added in some console.log statements to check the value
This code is definitely being called because availableTypes is being set
It's pulling it from the array it's looping from. How can this be an incorrect value?
It persists even if i pull the ng-selected out
It persists if I use ng-options instead of looping through them (though that then means i can't set the value to the be the same as the text without turning it into an object)
Any clues much appreciated.
James

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