Edit functionality in ng-controller - angularjs

I am trying to display a list of objects from a controller and then trying to edit them.
I am binding data called editContact to value in input text box. I want to set a variable ii in scope and then when editing is done, then replace contacts[ii] with the temporary object editContact. But ii is not being recognized. Can I set a variable like ii?
<!doctype html>
<html ng-app>
<head>
<style>
</style>
<link rel="stylesheet" type="text/css" href="style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.16/angular.min.js"></script>
</head>
<body>
<div ng-controller="contactsController">
<label>Name</label>
<input ng-model="name" type="text" placeholder="Name">
<label>email</label>
<input ng-model="email" type="text" placeholder="Email">
<button ng-click="addContact()">Add contact</button>
<div>{{contactsController.name}}</div>
<div>
<ul>
<li ng-repeat="contact in contacts">
<div>{{contact.name}}</div>
<div>{{contact.email}}</div>
<div><button ng-click="deleteContact($index)">delete</button></div>
<div><button ng-click="editContact($index)">Edit</button></div>
</li>
</ul>
<input type="text" value="{{editContact.name}}"/>
<input type="text" value="{{editContact.email}}"/>
<button ng-click="changeValue(ii)">Edit</button>
</div>
</div>
<script>
// Your code goes here.
// $( document ).ready(function() {
// alert('jQuery asdfas!');
// Your code here.
// });
function contactsController($scope){
$scope.contacts=[{name:'asdf',email:'asdf'},
{name:'yweuir',email:'xcvzx'}
];
contactsController.prototype.addContact =function(){
console.log(this.name);
console.log(this.email);
this.contacts.push({name:this.name,email:this.email});
}
$scope.changeValue=function(){
$scope.contacts[$scope.ii]=$scope.editContact;
}
$scope.editContact=function(i){
$scope.editContact=$scope.contacts[i]
$scope.ii=i;
}
}
</script>
</body>
</html>

First, in the DOM always bind attributes for input boxes with ngModel. Example:
<input type="text" ng-model="editContact.name"/>
Second, never use $index as a point of reference for finding things in your controller. You should use the object itself. Example:
<li ng-repeat="contact in contacts">
<button ng-click="editSomeContact(contact)"></button>
</li>
Then your JavaScript should look like this:
$scope.addContact = function() {
$scope.contacts.push({name: '', email: ''});
};
$scope.editSomeContact = function(contact) {
$scope.editContact = contact;
};
$scope.deleteContact = function(contact) {
var index = $scope.contacts.indexOf(contact);
if(index > -1) {
$scope.contacts.splice(index, 1);
}
if($scope.editContact === contact){
$scope.editContact = null;
}
};
At this point, you don't need a changeValue function because you'll see that the contacts in the list will update with the edited fields due to the fact that the values are dynamically bound in both places.

Related

Writing an adding function in AngularJS

I'm new to AngularJS and I am doing some tutorials to get in touch with it. While I'm doing the tutorials I have modified the code a bit to get a better feeling of what's behind. My code consists of two parts, which have nothing to do with each other.
The first one is a simple user input and based on that a list gets filtered. This is working fine.
However, in the second part I was trying to implement a simple adding function where the user can give an input and based on that the sum of two numbers is calculated. This part is not working at all. The numbers are being recognised as strings. The code is basically from this source here. When I copy the whole code and run it, it works fine, but when I modify it a bit it doesn't.
I want to understand why my code isn't working. To me there is nearly no difference. So I think that I eventually misunderstood the concept of angularjs. But I can't figure out where the error could be.
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script type="text/javascript">
function TodoCtrl($scope) {
$scope.total = function () {
return $scope.x + $scope.y;
};
}
</script>
</head>
<body data-ng-app>
<input type="text" ng-model="name">{{name}}
<div data-ng-init="Names=['Arthur', 'Bob', 'Chris', 'David', 'EDGAR']">
<ul>
<li data-ng-repeat="naming in Names | filter: name ">{{naming}}</li>
</ul>
</div>
<div data-ng-controller="TodoCtrl">
<form>
<input type="text" ng-model ="x">{{x}}
<input type="text" ng-model ="y"> {{y}}
<input type="text" value="{{total()}}"/>
<p type= "text" value="{{total()}}">value</p>
</form>
</div>
</body>
</html>
Several things to change...
First you need to create a module:
var app = angular.module("myApp", []);
Then you need to define a module e.g. myApp on the ng-app directive.
<body data-ng-app="myApp">
Then you need to add TodoCtrl to the module:
app.controller("TodoCtrl", TodoCtrl);
Also check that both $scope.x and $scope.y have values, and make sure that they are both parsed as integers, otherwise you will get string concatenation ("1"+"1"="11") instead of addition (1+1=2)!
$scope.total = function () {
return ($scope.x && $scope.y)
? parseInt($scope.x) + parseInt($scope.y)
: 0;
};
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script type="text/javascript">
(function(){
var app = angular.module("myApp", []);
app.controller("TodoCtrl", TodoCtrl);
function TodoCtrl($scope) {
$scope.total = function () {
return ($scope.x && $scope.y)
? parseInt($scope.x) + parseInt($scope.y)
: 0;
};
}
}());
</script>
</head>
<body data-ng-app="myApp">
<input type="text" ng-model="name">{{name}}
<div data-ng-init="Names=['Arthur', 'Bob', 'Chris', 'David', 'EDGAR']">
<ul>
<li data-ng-repeat="naming in Names | filter: name ">{{naming}}</li>
</ul>
</div>
<div data-ng-controller="TodoCtrl">
<form>
<input type="text" ng-model ="x">{{x}}
<input type="text" ng-model ="y"> {{y}}
<input type="text" value="{{total()}}"/>
<p type= "text" value="{{total()}}">value</p>
</form>
</div>
</body>
</html>
As mentioned in the above two answers adding TodoCtrl as controller instead function will make the snippet work.
REASON:
Angularjs framework above 1.3 does not support global function which means declaring controller as function wont work.
In your code snippet, you are using angular version 1.5, which needs the controller to be defined.
DEMO
angular.module("app",[])
.controller("TodoCtrl",function($scope){
$scope.x = 0;
$scope.y = 0;
$scope.total = function () {
return parseInt($scope.x) + parseInt($scope.y)
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" >
<input type="text" ng-model="name">{{name}}
<div data-ng-init="Names=['Arthur', 'Bob', 'Chris', 'David', 'EDGAR']">
<ul>
<li data-ng-repeat="naming in Names | filter: name ">{{naming}}</li>
</ul>
</div>
<div data-ng-controller="TodoCtrl">
<form>
<input type="text" ng-model ="x">{{x}}
<input type="text" ng-model ="y"> {{y}}
<input type="text" value="{{total()}}"/>
<p type= "text" value="{{total()}}">value</p>
</form>
</div>
</div>
you need to define the TodoCtrl as controller instead function
.controller("TodoCtrl",function($scope){
$scope.x = 0;
$scope.y = 0;
$scope.total = function () {
return parseInt($scope.x) + parseInt($scope.y)
};
})
Demo
angular.module("app",[])
.controller("TodoCtrl",function($scope){
$scope.x = 0;
$scope.y = 0;
$scope.total = function () {
return parseInt($scope.x) + parseInt($scope.y)
};
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" >
<input type="text" ng-model="name">{{name}}
<div data-ng-init="Names=['Arthur', 'Bob', 'Chris', 'David', 'EDGAR']">
<ul>
<li data-ng-repeat="naming in Names | filter: name ">{{naming}}</li>
</ul>
</div>
<div data-ng-controller="TodoCtrl">
<form>
<input type="text" ng-model ="x">{{x}}
<input type="text" ng-model ="y"> {{y}}
<input type="text" value="{{total()}}"/>
<p type= "text" value="{{total()}}">value</p>
</form>
</div>
</div>

Using Angular, how to display validation issues on an event?

I do not want to render validation issues until a user attempts to submit my form.
I have a form with 2 fields, one is required and ng-minlength=5, the other is ng-minlength=5. If the fields are invalid, I would like to display them with a red background if the user attempts to submit the form.
I am attempting to do this by determining the style in the controller based on the field's validity and if the submit button has been clicked.
This isn't working for me though, the field never displays as red. Any suggestions as to how I can get this approach to work?
Is this a reasonable approach, or is there a strategy more idiomatic to Angular?
https://jsfiddle.net/dk89dhp2/
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
</head>
<style>
.my_invalid{
border-color:#ffdddd;
background-color:#ffdddd;
background-image: none;
}
</style>
<body ng-app="myapp">
<div ng-controller="MyController" >
<form name="myFormNg">
<input type="text" class="myForm.getFormFieldCssClass(myFormNg.id)" name="id" ng-model="myForm.id" ng-minlength="5" required> Id <br/>
<input type="text" class="myForm.getFormFieldCssClass(myFormNg.name)" name="name" ng-model="myForm.name" ng-minlength="5"> Name <br/>
<button type="button" ng-click="myForm.submit()">Submit</button>
</form>
<script>
angular.module("myapp", [])
.controller("MyController", function($scope) {
$scope.myForm = {};
$scope.showErrors = false;
$scope.myForm.submit = function() {
$scope.showErrors = true;
}
$scope.myForm.getFormFieldCssClass = function(ngModelController) {
if (ngModelController.$pristine)
return "";
return ngModelController.$valid && $scope.showErrors ? "" : "my_invalid";
// additional logic to check if empty and required?
}
} );
</script>
now it's not working because you're using class that allow you to bind only "static" classes instead of the ng-class directive.
i'd change your code just to add the directive, about your approach, i'd stick with it, you need some sort of control that check if the form is valid and if the error are enabled.
<input type="text" ng-class="{'input1Error': myForm.getFormFieldCssClass(myFormNg.id})" />
<input type="text" ng-class="{'input2Error': myForm.getFormFieldCssClass(myFormNg.id})" />
then at the end of your method change the return type to boolean to determine whether to apply or not the class
$scope.myForm.getFormFieldCssClass = function(ngModelController) {
if (ngModelController.$pristine)
return false;
return ngModelController.$invalid && $scope.showErrors ? true : false;
}
you can try as below also.
<!DOCTYPE html>
<html>
<head>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.5/angular.min.js"></script>
</head>
<style>
.my_invalid{
border-color:#ffdddd;
background-color:#ffdddd;
background-image: none;
}
</style>
<body ng-app="myapp">
<div ng-controller="MyController" >
<form name="myFormNg">
<input type="text" class="myForm.getFormFieldCssClass(myFormNg.id)" id="textbox1" name="id" ng-model="myForm.id" ng-minlength="5" required> Id <br/>
<input type="text" class="myForm.getFormFieldCssClass(myFormNg.name)" id="textbox2" name="name" ng-model="myForm.name" ng-minlength="5"> Name <br/>
<button type="button" ng-click="myForm.submit()">Submit</button>
</form>
<script>
angular.module("myapp", [])
.controller("MyController", function($scope) {
$scope.myForm = {};
$scope.showErrors = false;
$scope.myForm.submit = function() {
angular.element('#textbox1').css('border-color', 'red');
angular.element('#textbox2').css('border-color', 'red');
}
$scope.myForm.getFormFieldCssClass = function(ngModelController) {
if (ngModelController.$pristine)
return "";
return ngModelController.$valid && $scope.showErrors ? "" : "my_invalid";
// additional logic to check if empty and required?
}
} );
</script>
</body>
</html>

angularjs checkbox ng-checked not working

I have the following code :-
<!DOCTYPE html>
<html lang="en">
<head>
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.4/angular.min.js"></script>
</head>
<body ng-app="app" ng-controller="ctrl" ng-init="init()">
<div class="container" style="width:400px">
<div class="panel panel-default">
<div class="panel-body">
<form>
<div class="form-group">
<label for="selectedBasket">Select basket :</label>
<select id="selectedBasket" class="form-control" ng-model="selectedBasket" ng-options="b.name for b in baskets">
</select>
</div>
<div ng-repeat="f in fruits" class="checkbox">
<label>
<input type="checkbox" value="" ng-checked="selectedBasket !== null && selectedBasket.items.indexOf(f) !== -1">
{{ f }}
</label>
</div>
</form>
</div>
</div>
</div>
<script>
var app = angular.module('app', []);
app.controller('ctrl', function($scope) {
$scope.init = function() {
$scope.baskets = [{'name': 'mary', 'items': ['apple', 'orange']}, {'name': 'jane', 'items': ['banana']}];
$scope.fruits = ['apple', 'banana', 'cherry', 'orange', 'watermelon'];
$scope.selectedBasket = null;
};
});
</script>
</body>
</html>
if I select Mary or Jane, I can correctly see the correct items in their basket checked. However if I manually check all the fruits and then look at Mary or Jane, it doesn't exclude the items that are not in their baskets. Why is ng-checked failing?
Bonus question, is it best practise to set selectedBasket to null and checking for null in a directive assuming I want nothing as a default value, is there a better way?
You've got no ng-model in your checkbox so your manual action isn't registered anywhere.
ng-checked is only used to make a 'slave' checkbox it can take no manual action.
My guess is you should use a ng-model initialized to your ng-check value instead of using a ng-checked.
If you want to keep your ng-checked what you can do is :
<input type="checkbox" ng-click="selectedBasket.items.push(f)" ng-checked="selectedBasket !== null && selectedBasket.items.indexOf(f) !== -1">
in fact it's still wrong... must be tired, use a toogle function in your ng-click which add or remove the item should be better...
Had the same problem with ng-check, tried everything but nothing worked. I wanted to control the number of checked Items when clicked to 2, so I used the $Event sent with ng-click and disable it.
Here is a sample code:
<input type="checkbox" ng-click="toggleCheck($event, product._id);"
ng-checked="isChecked(product._id)">
$scope.toggleCheck($event, productId){
if ( $scope.featuredProducts.indexOf(productId) === -1) {
if ($scope.featuredProducts.length < 2) {
$scope.featuredProducts.push(productId);
}else {
$event.preventDefault();
$event.stopPropagation();
}
} else {
$scope.featuredProducts.splice( $scope.featuredProducts.indexOf(productId), 1);
}
}
$scope.isChecked(productId){
return ($scope.featuredProducts.indexOf(productId) !== -1);
}

AngularJS app go back to inital state automatically

Following app shows three todo items at first and
after adding an new data, it shows updated lists for a moment and go back to the original state.
Could you tell me why does it go back to the initial state automatically?
link for Pluker
http://plnkr.co/edit/h6THusBe7AWFle5ixXzX?p=preview
==================================
<!DOCTYPE html>
<html ng-app="initExample">
<head>
<link data-require="bootstrap-css#*" data-semver="3.3.1" rel="stylesheet" href="//maxcdn.bootstrapcdn.com/bootstrap/3.3.1/css/bootstrap.min.css" />
<script src="https://code.angularjs.org/1.4.0-beta.5/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body class="well" ng-controller="ExampleController">
<h1> AngularJS Todo List</h1>
<p> Total <strong> {{todolist.length}} </strong> / Remain <strong> {{countRemain()}} </strong> </p>
<ul>
<li ng-repeat="todo in todolist" class="checkbox"> <input ng-model="todo.done" type="checkbox"> {{todo.title}}</li>
</ul>
<form name="newItemForm" class="form-inline" action="">
<div class="form-group">
<label class="sr-only" for="newItemText" placeholder="Type new ToDo"></label>
<input type="text" class="form-control" ng-model="newTodo" name="newItemText" placeholder="Type new Todo">
</div>
<button type="submit" ng-click="addNewTodo(newTodo)" class="btn btn-default"> Add </button>
</form>
</body>
</html>
============================
// Code goes here
var mymodule=angular.module('initExample', []);
mymodule.controller('ExampleController',
['$scope', function($scope) {
$scope.todolist = [
{done: true, title:'AngularJS study'},
{done: false, title:'music listening'},
{done: false, title:'run'}
];
$scope.countRemain = function() {
var count = 0;
var list = $scope.todolist;
angular.forEach(list, function(val, key) {
if(!list[key].done) count++;
});
return count;
};
$scope.addNewTodo = function(newTodo) {
todolist.push({done: false, title: newTodo});
};
}
]
);
Remove the action attribute from the form or add "preventDefault" from the button click.
Fixed the plunkr:
<form name="newItemForm" class="form-inline">
http://plnkr.co/edit/KFpbbdlDPbnIPAW43EaG?p=preview
P.S. also fixed the addNewTodo Function:
$scope.addNewTodo = function(newTodo) {
$scope.todolist.push({done: false, title: newTodo});
};
You need to remove action attribute from form definition, otherwise browser will try to submit it, reloading the page:
<form name="newItemForm" class="form-inline">
<!-- ... -->
</form>
Demo: http://plnkr.co/edit/MwTmGqzdELzUbrY82kKg?p=preview

Generate dynamic form input fields and collect field data in an array

I am stuck with this little task.
I need to generate form input fields dynamically by clicking 'add' button on the form.
The form is supposed to create DB table schema. So every input field is a DB table field name.
I am OK generating the fields dynamically but have trouble with gathering the actual data.
<form ng-controller="NewTableCtrl" ng-submit="submitTable()">
<input type='text' ng-model='table.title' placeholder='Title:'>
<input ng-repeat="field in fields" type='text' ng-model='table.fields' placeholder='Field:'>
<div>
<button type='submit'>Submit</button>
<button ng-click="addFormField()">Add</button>
</div>
</form>
.. and the controller
.controller('NewTableCtrl', function($scope) {
$scope.fields = [];
$scope.table = {};
$scope.addFormField = function () {
$scope.fields.push({});
}
$scope.submitTable = function () {
console.log($scope.table);
}
});
Looks simple. When I click 'Add' button it generates the new input field but it does it with the same model object (obveously). And that's where my misunderstanding lies. I thought that if I declare $scope.fields = [];in the controller then repeating field data will just go into the array. But it just echoes the input in every repeating input field. I understand now that this is how it is supposed to be with two way binding.
The reason I thought like this is by the analogy with an ordinary form submission where the repeating input field names become an array in the URL encoded form data.
So how do I solve this? The server needs to get an array of fields like this: fields: [field1, field2 ...] Do I need to generate input fields with different scope variable for each field? How do I do this?
Is this more complex then I thought and it needs to be a directive? If yes, please, show me how to do this.
Thanks.
Right now you are iterating $scope.fields. When you are adding a new field you push an empty object into $scope.fields, but every input's ng-model points to $scope.table.fields (which is non-existing until first input writes to it - then it will hold a string variable).
For this simple use case you could try:
app.controller('NewTableCtrl', function($scope) {
$scope.table = { fields: [] };
$scope.addFormField = function() {
$scope.table.fields.push('');
}
$scope.submitTable = function() {
console.log($scope.table);
}
});
And:
<input ng-repeat="field in table.fields track by $index" type='text' ng-model='table.fields[$index]' placeholder='Field:'>
Demo: http://plnkr.co/edit/6iZSIBa9S1G95pIMBRBu?p=preview
Take a look at this
Working Demo
html
<body>
<div ng-app=''>
<div ng-controller="questionCtrl">
<div>
<ul>
<li ng-repeat="elemnt in questionelemnt">
<div>
<div id={{elemnt.id}} style="display:inline" >
<span ng-model="elemnt.question" ng-hide="editorEnabled" ng-click="editorEnabled=true">
{{elemnt.question}}
</span>
<div ng-show="editorEnabled">
<input ng-model="elemnt.question" ng-show="editorEnabled" >
<button href="#" ng-click="editorEnabled=false">Done editing</button>
</div>
</div>
<div style="display:inline">
<span>
<input type="text" ng-model="elemnt.answer" placeholder="Answer" required/>
</span>
</div>
<span ng-hide="elemnt.length == 1">
<button ng-click="questionelemnt.splice($index, 1)">Remove</button>
</span>
</div>
<hr/>
</li>
<li>
<button ng-click="addFormField($event)">Add</button>
</li>
</ul>
</div>
<div>
<button ng-click="showitems($event)">Submit</button>
</div>
<div id="displayitems" style="visibility:hidden;">
{{questionelemnt}}
</div>
</div>
</div>
</body>
script
function questionCtrl($scope) {
var counter = 0;
$scope.questionelemnt = [{
id: counter,
question: 'Question-Click on me to edit!',
answer: ''
}];
$scope.addFormField = function ($event) {
counter++;
$scope.questionelemnt.push({
id: counter,
question: 'Question-Click on me to edit!',
answer: ''
});
$event.preventDefault();
}
$scope.showitems = function ($event) {
$('#displayitems').css('visibility', 'none');
}
}
Variation of tasseKATTs solution using a hashmap instead of an array.
This allows me to have a nice JSON object I can just for-in over in order to build my query filter.
http://plnkr.co/edit/CArP3Lkmn7T5PEPdXgNt?p=preview
<!DOCTYPE html>
<html>
<head>
<script data-require="angular.js#*" data-semver="1.3.0" src="//code.angularjs.org/1.3.0/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<style>
div{ margin: 1em;}
input{margin-left:1em;}
</style>
</head>
<body ng-controller="myCtrl">
<h2>Using a filter map tied to ng-model to create a filter object</h2>
<div ng-repeat="field in fields">
{{field}}<input ng-model=filters[field] />
</div>
<hr>
<h3>Filter</h3>
{{filters}}
<script>
var app=angular.module("app",[]);
app.controller("myCtrl",function($scope){
$scope.filters={};
$scope.fields=["name","address","phone","state"];
});
angular.bootstrap(document,["app"]);
</script>
</body>
</html>

Resources