Nesting angular binding in ng-repeat - angularjs

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";

Related

Angular 5 disable and enable checkbox based on condition

I'm trying to implement a validation in which i want to disable the button if a specific value entered by user matches the value returned from a service, below is my code:
In the component, i call the service which returns the usernames like below, here is the console log for (UserNames):
0:{Name: "John", userId: "23432"}
1:{Name: "Smith", userId: "12323"}
2:{Name: "Alan", userId: "5223"}
3:{Name: "Jenny", userId: "124"}
in the template, i use NgFor to iterate over the usernames like below
<div *ngFor="let id of UserNames let i = index;">
<input type="radio" name="radio" [checked]="UserNames.userid" (click)="Submit(UserInput)"> <span>Submit</span>
</div>
What i want to achieve is if i enter 23432 the button should disabled because the service already returns userId with this value, unless a new user id entered the button should be enabled.
So the general case of disabling a submit button in the way you're describing would look like:
<button type="submit" [disabled]="someValidationFn()" ...>
and someValidationFn() would, according to the use case you described, contain something like
return UserNames.find(name => { return name.userId === userInput;}));
where userInput is a separate property in the component bound to some user-entered value, presumably via an open text input like
<input name="userInput" [(ngModel)]="userInput" type="text" placeholder="Enter some user id">
But, from the markup snippet you pasted*, I'm not clear that you have that "text" input separate from the radio button group. If the radio button group is meant to have submit actions attached to its individual buttons (it shouldn't), then you're actually guaranteed that the user selection will contain a userId which exists in your UserNames array: the only inputs you're offering are based on the data which came from your service in the first place.
Based on the use case you're describing, I'm not sure why you'd have the radio button group. It sounds like you would just want that text input field with a validation method to make sure that user input does not already exist in the UserNames.
Because I wrote a bunch of abstract snippets there, I thought it might be helpful to show some basic html and js where I put it all together:
// html
<form submit="someSubmitAction()">
<input name="userInput" [(ngModel)]="userInput" type="text" placeholder="Enter some user id">
<button type="submit" [disabled]="someValidationFn()">Submit</button>
</form>
// js
/* don't forget your #Component annotation and class declaration -- I'm assuming these exist and are correct. The class definition encloses all the following logic. */
public userInput: string;
public UserNames: any[];
/* then some service method which grabs the existing UserNames on component's construction or initialization and stores them in UserNames */
public someValidationFn() {
return UserNames.find(name => { return name.userId === userInput;}));
}
public someSubmitAction() {
/* presumably some other service method which POSTs the data */
}
*speaking of the snippet you pasted, there are a couple of errors there:
*ngFor="let id of UserNames <-- you won't get an id by referencing into the UserNames array here; you'll get a member of the UserNames array in each iteration -- i.e., you'd get {Name: "John", userId: "23432"}, then {Name: "Smith", userId: "12323"}, and so on. That's not necessarily an error, but I'm assuming that, b/c you used id as your variable name, you were expecting just the userId field. Otherwise you'd have to use {{id.userId}} in each iteration to access the actual id.
And bob.mazzo mentions another issue with the use of the [checked] attribute

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]
};

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 adding 2 properties to associated collections

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!

How to use ng-repeat for dictionaries in AngularJs?

I know that we can easily use ng-repeat for json objects or arrays like:
<div ng-repeat="user in users"></div>
but how can we use the ng-repeat for dictionaries, for example:
var users = null;
users["182982"] = "{...json-object...}";
users["198784"] = "{...json-object...}";
users["119827"] = "{...json-object...}";
I want to use that with users dictionary:
<div ng-repeat="user in users"></div>
Is it possible?. If yes, how can I do it in AngularJs?
Example for my question:
In C# we define dictionaries like:
Dictionary<key,value> dict = new Dictionary<key,value>();
//and then we can search for values, without knowing the keys
foreach(var val in dict.Values)
{
}
Is there a build-in function that returns the values from a dictionary like in c#?
You can use
<li ng-repeat="(name, age) in items">{{name}}: {{age}}</li>
See ngRepeat documentation. Example: http://jsfiddle.net/WRtqV/1/
I would also like to mention a new functionality of AngularJS ng-repeat, namely, special repeat start and end points. That functionality was added in order to repeat a series of HTML elements instead of just a single parent HTML element.
In order to use repeater start and end points you have to define them by using ng-repeat-start and ng-repeat-end directives respectively.
The ng-repeat-start directive works very similar to ng-repeat directive. The difference is that is will repeat all the HTML elements (including the tag it's defined on) up to the ending HTML tag where ng-repeat-end is placed (including the tag with ng-repeat-end).
Sample code (from a controller):
// ...
$scope.users = {};
$scope.users["182982"] = {name:"John", age: 30};
$scope.users["198784"] = {name:"Antonio", age: 32};
$scope.users["119827"] = {name:"Stephan", age: 18};
// ...
Sample HTML template:
<div ng-repeat-start="(id, user) in users">
==== User details ====
</div>
<div>
<span>{{$index+1}}. </span>
<strong>{{id}} </strong>
<span class="name">{{user.name}} </span>
<span class="age">({{user.age}})</span>
</div>
<div ng-if="!$first">
<img src="/some_image.jpg" alt="some img" title="some img" />
</div>
<div ng-repeat-end>
======================
</div>
Output would look similar to the following (depending on HTML styling):
==== User details ====
1. 119827 Stephan (18)
======================
==== User details ====
2. 182982 John (30)
[sample image goes here]
======================
==== User details ====
3. 198784 Antonio (32)
[sample image goes here]
======================
As you can see, ng-repeat-start repeats all HTML elements (including the element with ng-repeat-start). All ng-repeat special properties (in this case $first and $index) also work as expected.
JavaScript developers tend to refer to the above data-structure as either an object or hash instead of a Dictionary.
Your syntax above is wrong as you are initializing the users object as null. I presume this is a typo, as the code should read:
// Initialize users as a new hash.
var users = {};
users["182982"] = "...";
To retrieve all the values from a hash, you need to iterate over it using a for loop:
function getValues (hash) {
var values = [];
for (var key in hash) {
// Ensure that the `key` is actually a member of the hash and not
// a member of the `prototype`.
// see: http://javascript.crockford.com/code.html#for%20statement
if (hash.hasOwnProperty(key)) {
values.push(key);
}
}
return values;
};
If you plan on doing a lot of work with data-structures in JavaScript then the underscore.js library is definitely worth a look. Underscore comes with a values method which will perform the above task for you:
var values = _.values(users);
I don't use Angular myself, but I'm pretty sure there will be a convenience method build in for iterating over a hash's values (ah, there we go, Artem Andreev provides the answer above :))
In Angular 7, the following simple example would work (assuming dictionary is in a variable called d):
my.component.ts:
keys: string[] = []; // declaration of class member 'keys'
// component code ...
this.keys = Object.keys(d);
my.component.html: (will display list of key:value pairs)
<ul *ngFor="let key of keys">
{{key}}: {{d[key]}}
</ul>

Resources