Angularjs binding not working on array of complex object - javascript

Given this controller:
angular.module("upload.app").controller("upload",[upload]);
function upload(){
var me = this;
me.uploadList = [{Name: "Test Upload",
Id: 1,
NewFiles: []
}];
me.selectedUpload = me.uploadList[0];
me.setSelected = function(upload) {
me.selectedUpload = upload;
}
...
me.addFilesToUpload = function(element){
me.selectedUpload.NewFiles = element.files;
}
and this html:
<div ng-controller="upload as vm">
<input id="filechooser" type="file" multiple onchange="angular.element(this).scope().vm.addFilesToUpload(this)" />
<table>
<tbody>
<tr ng-repeat="up in vm.uploadList" ng-click="vm.setSelected(up)">
<td>{{up.Name}}<br />{{up.NewFiles.length}}</td>
</tr>
</tbody>
</table>
<table>
<tbody>
<tr ng-repeat="file in vm.selectedUpload.NewFiles">
<td>{{file.name}}</td>
</tr>
</tbody>
</table>
</div>
I would expect that when the input onchange event calls addFilesToUpload() and the files are then added to the NewFiles property, that Angularjs would automatically update the view ... in this case, {{up.NewFiles.length}} value in the first table and the second table that lists the files.
However, nothing is being updated until I click on my row in the first table which, as you can see, fires the setSelected function on my controller.
How can I get Angular to refresh when the NewFiles property is changed as well?
Sorry, just fixed the fiddle -- forgot to save it originally
See this jsfiddle. Begin by clicking on the Test Upload. Now select files. Nothing happens. Click again on Test Upload and you'll see all the bindings refreshed.

Use $apply (DEMO):
$scope.$apply(function() {
$scope.selectedUpload.NewFiles = element.files;
});
This is usually done by angular but because you are using the native js event onchange you have to wrap it in an $apply callback yourself.

There is no default binding provided by angular to input type=file https://github.com/angular/angular.js/issues/1375, so you'll probably need to create your own directive or you can use angular-file-upload library.
Check out this answer from stackoverflow.

The problem with your updated fiddle is mainly this line
$scope.selectedUpload = null;
The moment you have selected the files and invoke the callback addFilesToUpload(), and assign the selected files to $scope.selectedUpload.NewFiles = element.files; then you'll definitely get an error:
Uncaught TypeError: Cannot set property 'NewFiles' of null
Simply change it back to your original code: $scope.selectedUpload = $scope.uploadList[0];
The next problem would be to update the current selected upload list, simply use $scope.$apply(), because you are using a native event onchange to update the $scope. Your callback should look this:
$scope.addFilesToUpload = function(element){
$scope.$apply(function() {
$scope.selectedUpload.NewFiles = element.files;
});
}
Check this updated fiddle.

Related

Angular ng-model in appended Html code not working

This is my code. Where I dynamically add html on a button click.but the ng-model which I gave there is not working.
Can anyone solve my issue
$scope.addParam = function(selected) {
if (selected == "bucket_policy_privacy_status") {
var myElement = angular.element(document.querySelector('#inputParameters'));
myElement.append('<input type="text" style="width:220px" class="form-control" name="" disabled="" value=' + selected + '> <select class="form-control" style="width:196px" ng-model="fields.privacyStatus" ><option value="range">Range</option><option value="public">Public</option><option value="private">Private</option></select> <br><br>');
$scope.$watch('fields.privacyStatus', function() {
var privacy_status = $scope.fields.privacyStatus;
alert();
var status = {
"term": {}
}
status.term[selected] = privacy_status;
output.push(status);
});
$('#params option[value="bucket_policy_privacy_status"]').remove();
$scope.input.select = "bucket_owner";
}
};
There are a few things you will need to change:
You need to use Angulars $compile option to evaluate angular attributes/expressions when inserting HTML dynamically.
Adding the line below after you added the element ot the DOM should do the trick:
$compile(angular.element('.form-control'))($scope);
It basically just let's angular know there is something new it should evaluate and start watching.
(Don't forget to add $compile to your module dependencies).
Further to that I assume you have actually added an object called fields on your $scope?
Another thing you could do is use ng-change on your form instead of using $watch. The function you bind on ng-change would be invoked every time one of the selects in your form changes.
For further reading have a look at this:
https://docs.angularjs.org/api/ng/service/$compile
I think that will not work, you are adding only dom, that's all, there's no bind.
One way is to create directive which will do that, or add that input in template with "ng-hidden", and on click just show.

How to stop AngularJS from Binding in Rows

I am using Angularjs 1.5.3 I have 2 services one service calls Area names, the other calls the details for the Area.
So in my code, I call the first service to get the Area, then I set the ng-init to call the details. This works fine, however angular keeps only the first value for all the rows.
Here is the code;
<tbody data-ng-repeat="area in vm.Areas" ng-init='vm.getDetails(area)'>
<tr>
<td class="text-bold">{{area}}</td>
<td>{{vm.AreaDetails.Employees}}</td>
<td>{{vm.AreaDetails.Hours}}</td>
<td>{{vm.AreaDetails.Sales}}</td>
</tr>
</tbody>
Any ideas on fixing this?
Thanks
You should avoid using ng-init for this. It's an abuse of ng-init and decrease your performance drastically. See: ngInit. Try to get your details before you start rendering eg (pseydo):
vm.areas = vm.areas.map(function(area) {
return area.details = service.getDetails(area);
}
#TJ answer is right on the technical part however i think you have a design problem in your code.
If you want to load area and their details you should load all of them in one go.
Instead you'll load them one by one there.
So let's say you have 10 Area and you're Detail service load data from (i suppose) the server : that makes 11 requests : 1 for all area, 10 for details of each area.
So just load all the whole thing in one call to your service (and presumably the server) and perform a simple ng-repeat.
You can simply have the controller iterate over the areas and call getDetails for each of them and append the detail to the respective area when they arrive.
The bindings will be along:
<tbody data-ng-repeat="area in vm.Areas">
<tr>
<td class="text-bold">{{area}}</td>
<td>{{area.details.Employees}}</td>
<td>{{area.details.Hours}}</td>
<td>{{area.details.Sales}}</td>
</tr>
</tbody>
The bindings will be updated when the data arrive.
Or you can use a directive with isolated scope, something like the following:
angular.module('yourModule').directive('areaInfo', function() {
return {
scope: {
area: '=areaInfo'
},
require: "yourController", // conroller where getDetails is defined
templateUrl: "area-info.html",
link: function(scope, element, attrs, ctrl) {
scope.areaDetails = ctrl.getDetails(scope.area);
}
}
});
<script type="text/ng-template" id="area-info.html">
<tr>
<td class="text-bold">{{area}}</td>
<td>{{areaDetails.Employees}}</td>
<td>{{areaDetails.Hours}}</td>
<td>{{areaDetails.Sales}}</td>
</tr>
</script>
<tbody data-ng-repeat="area in vm.Areas" area-info="area"></tbody>
You can even move the getDetails method to the directive itself.

get the text of div using angularjs

i want to get the text of div using angularjs . I have this code
<div ng-click="update()" id="myform.value">Here </div>
where as my controller is something like this
var myapp= angular.module("myapp",[]);
myapp.controller("HelloController",function($scope,$http){
$scope.myform ={};
function update()
{
// If i have a textbox i can get its value from below alert
alert($scope.myform.value);
}
});
Can anyone also recommand me any good link for angularjs . I dont find angularjs reference as a learning source .
You should send the click event in the function, your html code should be :
<div ng-click="update($event)" id="myform.value">Here </div>
And your update function should have the event parameter which you'll get the div element from and then get the text from the element like this :
function update(event)
{
alert(event.target.innerHTML);
}
i just thought i put together a proper answer for everybody looking into this question later.
Whenever you do have the desire to change dom elements in angular you need to make a step back and think once more what exactly you want to achieve. Chances are you are doing something wring (unless you are in a link function there you should handle exactly that).
So where is the value comming, it should not come from the dom itself, it should be within your controller and brought into the dom with a ng-bind or {{ }} expression like:
<div>{{ likeText }}</div>
In the controller now you can change the text as needed by doing:
$scope.likeText = 'Like';
$scope.update = function() {
$scope.likeText = 'dislike';
}
For angular tutorials there is a good resource here -> http://angular.codeschool.com/
Redefine your function as
$scope.update = function() {
alert($scope.myform.value);
}
A better way to do it would be to use ng-model
https://docs.angularjs.org/api/ng/directive/ngModel
Check the example, these docs can be a bit wordy

Add items to drop-down box

I can't seem to find the correct syntax to get this working:
$.get('/templates/mytemplate.html', function (template) {
$(template).find('select').append($("<option />").val(0).text('Please select ...'));
$.each(dashboard.myArray, function () {
$(template).find('select').append($("<option />").val(this.Id).text(this.Text));
});
$('#new-items').append(template);
});
The template variable is just a string of html like:
"<form class="user-item">
<select class=".sc" name="context" />
<input type="hidden" name="id"/>
<input type="hidden" name="date"/>
<form>"
I've tried selecting the select item on name 'select[name=context]' and using a class selector like '.sc' as well ... none seem to work but I've got similar code working fine elsewhere. Very confused.
The problem is template is a string. in your case you are creating a new jQuery wrapper for that element every time and manipulating it but that does not actually change the contents of the string in template, it just changes another in memory object
You need to create a reference to a new jQuery wrapper for template then do the dom manipulation using that reference and at the end append it to the container element
$.get('/templates/mytemplate.html', function (template) {
var $template = $(template);
$template.find('select').append($("<option />").val(0).text('Please select ...'));
$.each(dashboard.myArray, function () {
$template.find('select').append($("<option />").val(this.Id).text(this.Text));
});
$('#new-items').append($template);
});
Demo: Problem, Solution
such code will work
var value = 'some_value',
text = 'some_text';
$('#id').append($('<option value='+value+'>'+text+'</option>'));
the reason your code it's not working is you are modifying a variable but don't assign the changes to variable
$(template).find('select').append($("<option />").val(0).text('Please select ...'));
this line never stores changes to template, it should be :
template = $(template).find('select').append($("<option />").val(0).text('Please select ...'));

ng-click not firing in AngularJS while onclick does

I am trying to use AngularJS in my application and have been successful to some extent.
I am able to fetch data and display it to the user. And I have a button in ng-repeat via which I want to post DELETE request. Below is my code which does it.
<div class="navbar-collapse collapse">
<table class="table table-striped" ng-controller="FetchViewData">
<tr>
<td>Name</td>
<td>ID</td>
<td>Department</td>
<td></td>
</tr>
<tr ng-repeat="d in viewData">
<td>{{d.EmployeeName}}</td>
<td>{{d.EmployeeID}}</td>
<td>{{d.EmployeeDepartment}}</td>
<td>
<button class="trashButton" type="button"
name="view:_id1:_id2:_id14:_id24:btnDelete"
id="view:_id1:_id2:_id14:_id24:btnDelete"
ng-click="deleteRecord('{{d['#link'].href}}')">
<img src="/trashicon.gif"></button>
</td>
</tr>
</table>
</div>
This is the FetchViewData function which fetches the information and displays it to the user.
function FetchViewData($scope, $http) {
var test_link = "<MY LINK>";
$http.get(test_link).success( function(data) {
$scope.viewData = data;
});
}
The data is fetched and properly displayed.
But the code in ng-click="deleteRecord('{{d['#link'].href}}')" does not fire when delete button is clicked. In Google Chrome's developer tools I can see valid values are generated for code {{d['#link'].href}} but the code deleteRecord does not get fired. From this question I tried removing the braces and writing only d['#link'].href but it didn't work for me.
When I replace ng-click with onclick the deleteRecord function gets fired.
function deleteRecord(docURL) {
console.log(docURL);
$http.delete(docURL);
}
But then I receive the below error.
Uncaught ReferenceError: $http is not defined
deleteRecord
onclick
I am using jQuery 1.10.2 and AngularJS v1.0.8.
FetchViewData here is a controller, and in your html, where you have ng-controller="FetchViewData", you are telling it to look within that controller's scope for any angular methods and variables.
That means, if you want to call a method on click, it needs to be calling something attached to your controller's scope.
function FetchViewData($scope, $http) {
var test_link = "<MY LINK>";
$http.get(test_link).success( function(data) {
$scope.viewData = data;
});
$scope.deleteRecord = function(docURL) {
console.log(docURL);
$http.delete(docURL);
}
}
Here, the function exists on the scope, and any html that is inside your FetchViewData Controller has access to that scope, and you should be able to call your methods.
It's working when you use on-click because your function exists in the global namespace, which is where on-click is going to look. Angular is very heavily reliant on scoping to keep your namespaces clean, there's lots of info here: https://github.com/angular/angular.js/wiki/Understanding-Scopes
INSTEAD of this
ng-click="deleteRecord('{{d['#link'].href}}')"
TRY this
ng-click="deleteRecord(d['#link'].href)"
You don't need to use curly brackets ({{}}) in the ng-click
ENJOY...
function deleteRecord(docURL) {
console.log(docURL);
$http.delete(docURL);
}
It should be
$scope.deleteRecord = function (docURL) {
console.log(docURL);
$http.delete(docURL);
}
EDIT:
change something in html and controller ....
SEE WORKING DEMO
The deleteRecord method should be assigned in the current and correct scope
$scope.deleteRecord = function(){
....
Another possibility for why ng-click does not fire, is that you are apply a CSS style of pointer-events:none; to the element. I discovered that Bootstrap's form-control-feedback class applies that style. So, even though it raises the z-index by 2 so that the element is in front for clicking, it disables mouse-clicks!
So be careful how your frameworks interact.
As mentioned, the function should be created inside the scope:
$scope.deleteRecord = function (docURL) {
console.log(docURL);
$http.delete(docURL);
}
To use the function, first drop the "{{ }}" since you are using it from inside an ng-repeat. Another issue is the use of apostrophe in your code, you have two pairs one inside the other... well I am sure you get the problem with that.
Use the function like so:
ng-click="deleteRecord(d['#link'].href)"
Best of luck !
If you want to use as a submit button the set the type to 'submit' as:
<button type="submit" ...

Categories