I am currently facing an issue whereby I would like to generate a dynamic number of input forms and also name them dynamically (as defined by a JSON) so that I can reference them separately.
For example, if my JSON object had three items in it, I would generate three input boxes with ng-model="1", ng-model="2" and ng-model="3" respectively. In real life the ID's will come from the JSON itself.
I'm currently using ng-repeat to generate the forms in a table;
<tr ng-repeat="x in names track by $index">
<td>{{ $index }}</td> <!-- this outputs fine -->
<td>{{ x.Description }}</td>
<td>{{ x.Metric }}</td>
</tr>
And using a directive & the $compile function to dynamically generate an input form with a unique ng-model name.
app.directive("outDynamic", function($compile){
return{
link: function(scope, element, attrs){
var template = "<input type='number' ng-model='" + scope.ray[attrs.element1] + "'>";
var linkFn = $compile(template);
var content = linkFn(scope);
element.append(content);
}
} });
However: none of the following works (when nested in the ng-repeat)
<!-- None of these work -->
<td out-dynamic element1=$index></td>
<td out-dynamic element1="$index"></td>
<td> <input type='number' ng-model="array[$index]"> </td>
<td> <input type='number' ng-model="array['$index']"> </td>
Summary of issue;
Every time I try and reference the $index tracker, I get an
undefined error in my directive code.
You should use {{}} to assign $index value for attribute. so use
element1={{$index}} instead of element1=$index
<tr ng-repeat="x in names track by $index">
<td out-dynamic element1={{$index}}></td>
<td out-dynamic element1="{{$index}}"></td>
</tr>
I guess your scope.arr is perfect.
PLUNKER DEMO
Related
In my fees-list.component.html template, I have something like the following:
<div class="container">
<div class="row" *ngFor="let meta of fees">
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>Amount owing (AUD)</th>
<th>Fee type</th>
<th>Title</th>
<th>Amount to pay (AUD)</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let fee of meta.fee">
<td>{{ fee.balance }}</td>
<td>{{ fee.type.desc }}</td>
<td>{{ fee.title }}</td>
<td><input id="{{ fee.id }}" type="text" name="toPay" value="{{ fee.balance }}"></td>
</tr>
</tbody>
</table>
</div>
<div class="row">
<div class="col-md-4"><strong>Total to pay: </strong><input type="text" name="total" value="{{ meta.total_sum }}"> </div>
</div>
</div>
</div>
The requirement for my interface is twofold. If users change the value of an individual fee (fee.balance), the value in the total text input field should be updated.
Secondly, if the total input field is updated, I need to update the value(s) in the individual fees accordingly (reducing those by the appropriate amount from oldest fee to newest fee).
My question is, how do I do binding for these input fields which are dynamically generated, though they do have unique ids (id="{{ fee.id }})? I cannot work out how to target an individual fee field in my typescript file.
Use two way binding with ngModel
<input [name]="fee.id" type="text" [(ngModel)]="fee.balance">
Now when the user types in the textbox it will update the value in the array directly.
Also you should not be using {{}} binding on your attributes like that, use attribute binding with the [box] syntax.
<input type="text" name="total" [value]="total_sum">
You can calculate the total in your TypeScript like
get total_sum() {
return meta.fees.reduce((total, fee) => total + fee.balance, 0);
}
One approach ( preferred) is to create the controls dynamically.
your typescript file :
constructor(){
this.formGroup = new FormGroup();
const controls = meta.fee.map((fee)=>{
this.formGroup.addControl(fee.id, new FormControl(fee.balance))
});
// now you have a formGroup with all the controls and their value are initialized, you just need to use it in your template
}
your template :
<tbody>
<tr *ngFor="let fee of meta.fee">
<td>{{ fee.balance }}</td>
<td>{{ fee.type.desc }}</td>
<td>{{ fee.title }}</td>
<td><input id="{{ fee.id }}" [formControl]="formGroup.get(fee.id)" type="text" name="toPay"></td>
</tr>
</tbody>
So obviously, after this, you have the full power to do anything, for example, if you want to update a specific one of them:
updateValue(){
this.formGroup.get('oneOfThoseFeeIds').setValue('new value')
}
NOTE :
I haven't done Angular for a while and don't remember the exact syntaxes, but I hope this gives you a path
Can't you just use a service for that? I guess this is a way to go here, since you should try keeping your components as small as possible and communicate them by services.
Please, have a look at this chapter in angular documentation: https://angular.io/tutorial/toh-pt4
I'm using AngularJS to display some data on a table, for that I'm making use of ng-repeat which takes an object and displays the values of the properties of it in the table cells.
The object structure is like this:
{
...
...
"containerKey" : {
"parentKey" : {
"childKey1" : "value",
"childKey2" : "value",
"childKey3" : "value
}
}
}
The ng-repeat shows a table
<table ng-repeat="key in containerKey">
<tr>
<!-- This should display the value of the key (in my case it is the name of a day in the week, but it displayed the entire object inside key.parentKey, which is expected -->
<th id="day">{{ key.parentKey }}</th> <!-- I would like for this to show the value of the key, in my case that would be the name of the day, like "monday" -->
</tr>
<tr>
<td>{{ parentKey.childKey1 }}</td>
</tr>
<tr>
<td>{{ parentKey.childKey2 }}</td>
</tr>
<tr>
<td>{{ parentKey.childKey3 }}</td>
</tr>
</table>
How would I go about showing only the key value of the parentKey in the table cell? Consider that I'm using ng-repeat to show multiple rows and each of this rows contain the ng-repeat that contains the days (parentKey).
To clarify:
I would like the <th> element with id="day" to show the text of the parentKey key. The th element's content would be parentKey (the string parentKey), literally, instead of the value of parentKey.
Have you tried altering your ng-repeat to use the object notation:
<table ng-repeat="(key, value) in containerKey">
You should have access to the key then.
Firstly, in your code you are trying to use ng-repeat over an object. ng-repeat should iterate over arrays.
Considerating containerKey is an array of parentKey, this may works:
<table>
<tbody ng-repeat="(key, value) in containerKey">
<th>{{key}}</th>
<tr>{{value.childKey1}}</tr>
<tr>{{value.childKey2}}</tr>
<tr>{{value.childKey3}}</tr>
</tbody>
</table>
I have a table that is populated with ngRepeat and I have a input[text] where you can filter the table.
This works fine but now I came up with the idea to have the possibility to double-click on an element in the table and add the text to the search input[text] so the filter is applied straight when you double-click on the text.
Unfortunately it does not work as expected.
I have done this:
<input type="text" placeholder="Search..." data-ng-model="userinput" />
<p data-ng-dblclick="userinput='query'">Double click to use query to search</p>
And in the ngRepeat I use the ng-model "userinput" to filter but the value of the text input is not changing.
I also tried to specify the model "userinput" as variable in the controller and then change it per function but it is not working.
Is there something I'm missing?
Normally I would change the variable in the controller and it should automatically change the text input since it uses this variable as model. Then with this it should change the filter too but nothing happens.
WORKING
Code ngRepeat
<tr data-ng-repeat="dat in data | filter: userInput | filter: tsSelect | filter: advSelect | filter: checkedFilter | orderBy: ['client', 'ssrstatus'] | limitTo: totalDisplay" id="{{ dat.bannerid }}"> <!-- | unique: 'bannerid' | filter: errorSelect| -->
<td>
<input type="checkbox" id="checked" data-ng-model="dat.checked" data-ng-change="updateCheckedStatus(dat._id['$id'], dat.checked)">
<label for="checked">Checked</label>
</td>
<td data-ng-dblclick="search(dat.clientid)">{{ dat.clientid }}</td>
<td data-ng-dblclick="search(dat.client)" class="txtleft">{{ dat.client }}</td>
<td data-ng-dblclick="search(dat.tsengineer)">{{ dat.tsengineer }}</td>
<td data-ng-dblclick="search(dat.bannerid)">{{ dat.bannerid }}</td>
<td data-ng-dblclick="search(dat.bannertype)" class="txtleft">{{ dat.bannertype }}</td>
<td data-ng-dblclick="search(dat.width + 'x' + dat.height)">{{ dat.width == 0 ? 0 : dat.width - 50 }}x{{ dat.height == 0 ? 0 : dat.height - 50 }}</td>
<td data-ng-dblclick="search(dat.ssrstatus)" class="txtleft">{{ dat.ssrstatus }}</td>
<td data-ng-dblclick="search(dat.datebegin)">{{ dat.datebegin }}</td>
<td data-ng-dblclick="search(dat.dateupdated)">{{ dat.dateupdated }}</td>
<td>
<button class="preview {{ dat.bannerid }}" data-ng-click="showPreview(dat.bannerid, dat.clicktotestbanner, dat.width, dat.height)"></button>
</td>
<!-- <td id="{{ dat.bannerid }}" class="banner-preview"></td> -->
Controller
$scope.userInput = "";
$scope.search = function(query){
$scope.userInput = query;
}
I think it's because of your userinput='query' evaluated inside ng-repeat.
Let's name your outer scope "scopeA". The ng-model="userinput" of the search input would be referencing scopeA.userinput.
As we know, a new scope is created for every ng-repeat items. If you run userinput='query' in one of these scopes (name it scopeB), you would be assigning 'query' to scopeB.userinput instead of scopeA.userinput.
In this situation, scopeB is likely to be a child of scopeA. If you use angular-batarang Chrome extension to have a look at the scope tree, you would find both scopes to have userinput field.
One solution would be to use a function to assigning value to userinput instead of ng-dblclick expression. Like:
<p data-ng-dblclick="setUserinput('query')">Double click to use query to search</p>
And add a function setUserinput to your scope:
$scope.setUserinput = function(newValue) {
$scope.userinput = newValue;
}
So I'm calling a JSON string, and I want to split it in two at the ":" and use the two values to show in two separate td tags.
function testCtrl($scope) {
$scope.response = {"name":["The name field is required."],"param":["Hobby: Coding", "Country: USA"]};}
to visualize a little more at what I'm trying to do http://jsfiddle.net/8mnxLzc1/5/
You could try this:
<table>
<tr ng-repeat="resp in response.param">
<td ng-repeat="val in resp.split(':')">{{ val }}</td>
</tr>
</table>
Fiddle
This is a entirely angular solution, no need to call the function split in the ng-repeat.
https://jsfiddle.net/kLjzh565/
<table>
<tr ng-repeat="resp in response.param track by $index">
<td ng-repeat="i in [$index]">
{{response.param[i]}}
</td>
</tr>
</table>
I have a table where the last column in each row contains a little loading icon which I would like to display when a button inside the table is clicked.
When each table row is generated with ng-repeat, the loader shows up in every row rather than the individual one. How can I set ng-show to true or false for only the current index clicked?
Template:
<tr ng-repeat="record in records">
<td>{{ record.name }}</td>
<td><a ng-click="someAction(record.name)">Some Action</a></td>
<td ng-show="loading">Loading...</td>
</tr>
Controller:
$scope.someAction = function(recordName) {
$scope.loading = true;
};
You can pass in the $index parameter and set/use the corresponding index. $index is automatically available in the scope of an ng-repeat.
<td><a ng-click="someAction(record.name, $index)">Some Action</a></td>
<td ng-show="loading[$index]">Loading...</td>
$scope.someAction = function(recordName, $index) {
$scope.loading[$index] = true;
};
Here's a generic sample with all the logic in the view for convenience: Live demo (click).
<div ng-repeat="foo in ['a','b','c']" ng-init="loading=[]">
<p ng-click="loading[$index]=true">Click me! Item Value: {{foo}}<p>
<p ng-show="loading[$index]">Item {{$index}} loading...</p>
</div>
There are many ways to handle this.
The problem here is that your variable loading is sharing the scope between the rows.
One approach could be use $index
HTML
<tr ng-repeat="record in records">
<td>{{ record.name }}</td>
<td><a ng-click="someAction(record.name, $index)">Some Action</a></td>
<td ng-show="loading">Loading...</td>
</tr>
JS
$scope.someAction = function(recordName, $index) {
$scope.loading[$index] = true;
};
Using a property in your object record:
HTML
<tr ng-repeat="record in records">
<td>{{ record.name }}</td>
<td><a ng-click="someAction(record)">Some Action</a></td>
<td ng-show="record.loading">Loading...</td>
</tr>
JS
$scope.someAction = function(record) {
var name = record.name;
record.loading = true;
};
Best regards
The scope inside ng-repeat is different form the one outside. Actually the scope outside ng-repeat is the parent of the one inside. So the html code goes here
<tr ng-repeat="record in records">
<td>{{ record.name }}</td>
<td><a ng-click="someAction(record)">Some Action</a></td>
<td ng-show="$parent.loading">Loading...</td>
</tr>