Resetting form controls after action in Angular - angularjs

Need a bit of help with this. I am trying to get Angular to reset value of my input box after add control is invoked. This is kind of important when it comes to drop down select boxes. I want to be able to clear what user has selected after the option is pushed to a collection.
I also seems to have an issue with user.fName updating whatever I previously added to the users array but it's probably related to the above.
Also can you see what I am doing wrong with ng-show?
html
<div ng-controller="UserController" name="form">
<div ng-show="form.fName.$touched">Hello {{ user.fName }}</div>
<input type="text" name="fName" ng-model="user.fName">
<a ng-click="addUser(user)">Add</a>
<br>
<div ng-repeat="user in users">{{ user }}</div>
</div>
javascript
function UserController($scope) {
//$scope.user = {
// fName: "Joe",
// lName: "Doe"
//};
$scope.users = [];
$scope.addUser = function (user) {
if($scope.users.indexOf(user) === -1){
$scope.users.push(user);
} else {
// trigger error/notification message
console.log(user, ' already added');
}
user = {}; //clear user
};
}

ngModel will try to bind to the property given by evaluating the expression on the current scope. If the property doesn't already exist on this scope, it will be created implicitly and added to the scope.
You are currently not using the scoped property in your click action but a copy that is passed as a parameter. Change user to $scope.user in your addUser method
Note: your object comparison to identify already added users won't work as you always create new objects that will never match. Change it to some different comparison method like comparing the fName properties.
html
<div ng-controller="UserController" name="form">
<div ng-show="form.fName.$touched">Hello {{ user.fName }}</div>
<input type="text" name="fName" ng-model="user.fName">
<a ng-click="addUser()">Add</a>
<br>
<div ng-repeat="user in users">{{ user }}</div>
</div>
javascript
function UserController($scope) {
$scope.users = [];
$scope.addUser = function () {
if($scope.users.indexOf($scope.user) === -1){
$scope.users.push($scope.user);
} else {
// will never match as indexOf() will always return -1
console.log($scope.user, ' already added');
}
$scope.user = {}; //clear user
};
}
http://plnkr.co/edit/N5FXJdWRl42YrVwJsUuR?p=preview
As for your ng-show question: $touched was introduced in AngularJS 1.3 and you're referencing AngularJS 1.2.x in your Plunker code.

Related

Angular returns [object Object]

my Angular code returns [object Object]. I am calling 2 controllers on different pages. First one sets the data on ng-click and the second one gets (displays) the data. Here is the code:
Angular App code:
var careerApp = angular.module("careerApp", []);
careerApp.factory('myService', function () {
var savedData = {};
function set(data) {
savedData = data;
}
function get() {
return savedData;
}
return {
set: set,
get: get
}
});
careerApp.controller("JobList", function ($scope,myService) {
myService.set(data);
});
careerApp.controller("JobSelection", function ($scope, myService) {
$scope.jobname = myService.get();
});
HTML on Page 1
<div class="center-details" ng-controller="JobList">
<div class="details" ng-click="set(data)" >
<h2 class="name" ng-model="jobtitle">
Winter
</h2>
<p><b>Job ID#</b> <span class="jobid">2017-01</span></p>
</div>
</div>
HTML on Page 2
<div ng-controller="JobSelection">
<label ng-bind="jobname"></label>
</div>
You are bringing the whole object in
<label ng-bind="jobname"></label>
If you intented to write the object with a better formatting try changing it to:
<label> {{ jobname | json }}</label>
This way it will be formatted and printed as a json object.
Use Angular expressions intead of ng-bind. Otherwise you will have to specify a specific property of your object.
page 1
<div class="center-details" ng-controller="JobList">
<div class="details" ng-click="set('Winter')" >
<h2 class="name">
Winter
</h2>
<p><b>Job ID#</b> <span class="jobid">2017-01</span></p>
</div>
</div>
Controller:
careerApp.controller("JobList", function ($scope,myService) {
$scope.set= function(data){
myService.set(data);
}
});
page 2
<label ng-bind="jobname"></label>
<label>{{jobname}}</label>
How to make it dynamically, based on the input
<input stype="text" ng-model="jobTitle" ng-change="set()" >
<h2 class="name">
{{jobTitle}}
</h2>
Controller:
careerApp.controller("JobList", function ($scope,myService) {
$scope.jobTitle = "";
//This function will be called every time that jobTitle change its value.
$scope.set= function(){
myService.set($scope.jobTitle);
}
});
Notes:
Take into account that ng-model directive binds an input, select, textarea value to a property on the scope.
Since you have this assignment in your controller definition
$scope.jobname = myService.get();
If you run this controller before the user make a click it will be empty. it wont be refreshed in every click.

Angular remote form not displaying error

I have created an angular form that displays input validation errors received from the server. My solution works fine except for one small issue.
If I submit the form with no value, after the page loads, I am receiving the correct response from my server i.e. 422, but the validation error is not displayed. If I then start typing a value in the input the validation error flashes and disappears.
I am almost certain that it has something to do with my directive, but I'm not sure how to fix it. This is my current directive code:
var appServices = angular.module('webFrontendApp.directives', []);
appServices.directive('serverError', function(){
return {
restrict: 'A',
require: '?ngModel',
link: function(scope,element,attrs,ctrl){
element.on('change keyup', function(){
scope.$apply(function(){
ctrl.$setValidity('server', true);
});
});
}
};
});
I think the issue is with the element.on('change keyup'.... section of this code. That's why the error message flashes when I start typing. Also when I change this to 'change' instead of 'change keyup', the error message is displayed permanently when I start typing.
Does anybody have an idea of how I can display the error message even if I did not type any value into the input before submitting it the first time?
UPDATE AS PER COMMENT
Here is my form:
<form ng-submit="create(memberData)" name="form" novalidate>
<div class = "row form-group" ng-class = "{ 'has-error' : form.email.$dirty && form.email.$invalid }">
<input type="text" ng-model="memberData.email" placeholder="janedoe#mail.com" name="email" class="col-xs-12 form-control" server-error>
<span class="errors" ng-show="form.email.$dirty && form.email.$invalid">
<span class="glyphicon glyphicon-remove form-control-feedback"></span>
<span ng-show="form.email.$error.server">{{errors.email}}</span>
</span>
</div>
<div class="row">
<button type="submit" class="btn btn-danger col-xs-12">Join Private Beta</button>
</div>
</form>
And my controller:
$scope.memberData = {};
$scope.create = function() {
var error, success;
$scope.errors = {};
success = function() {
$scope.memberData = {};
};
error = function(result) {
angular.forEach(result.data.errors, function(errors, field) {
$scope.form[field].$setValidity('server', false);
$scope.errors[field] = errors.join(', ');
});
};
BetaMember.save({ beta_member: { email: $scope.memberData.email || "" }}).$promise.then(success, error);
};
Since the form itself doesn't have a $setValidity method, because doens't have an ng-model, and assuming that the server error is not referred to a single field ( in this case a $setValidity method is preferred ), I think that the simplest solution could be this one:
Create a form with some random validation ( this one simply needs at least the username ) and a div that can display a custom server error.
<div ng-controller="AppCtrl">
<form novalidate name="createForm">
<div class="inputWrap">
<input ng-model="name" name="name" type="text" required placeholder="John Doe">
<span ng-if="createForm.name.$dirty && createForm.name.$invalid">
Some kind of error!
</span>
</div>
<div ng-if="serverError">
{{ serverError.message }}
</div>
<input
value="Join Private Beta"
ng-disabled="createForm.$invalid || serverError"
ng-click="create()"
type="button">
</form>
</div>
Then in your controller you can add the create method that deal with YourService ( should return a promise ) and if the response is failure you can create a simple object with a custom server error message inside which will also be usefull to disable the form button, if you need.
var AppCtrl = function($scope, YourService){
// Properties
// Shared Properties
// Methods
function initCtrl(){}
// Shared Methods
$scope.create = function(){
YourService.makeCall().then(function(response){
// Success!
// Reset the custom error
$scope.serverError = null;
}, function(error){
// Do your http call and then if there's an error
// create the serverError object with a message
$scope.serverError = {
message : 'Some error message'
};
})
};
// Events
// Init controller
initCtrl();
};
AppCtrl.$inject = [
'$scope',
'YourService'
];
app.controller('AppCtrl', AppCtrl);
I mean it's very simple snippet here, I just wanted to bring an example. Nothing to complex, but you can scale this to something more.
It seems that there was an extremely simple workaround. Since I had the filter form.email.$dirty in my view, the error would not be displayed if the user clicked submit without clicking on the form first.
After removing form.email.$dirty and only having form.email.$invalid, it works perfectly. I think this is sufficient in my case, as this validation is dependant on a server response and will not be triggered before the form is submitted. The error object is also cleared up in my controller ensuring that the page does not load an error when it's first loaded.

AngularJS checkbox checking when in ng-repeat

I know the solution is not very pretty, but it's needed for the template I'm working in.
Now it works, and it shows me the "Added" span-element when the products was in the list.
<section ng-repeat="product in products">
<label>
<input type="checkbox"
checklist-model="foobar.products"
checklist-value="product.id">
{{ product.name }}
</label>
<div ng-repeat="current_item in my.profile.items">
<span ng-show="product.id==current_item.id">Added</span>
</div>
</section>
What I want, it to check the checkbox to checked, when the "Added" text also appeared behind the checkbox.
I tried doing it with:
<ng-checked="{{my.profile.items.array.thing}}">
But that's not working. Because the products in an Array like:
[{
id: 1, name:"Trinity",
id: 2, name:"Van Gogh",
id: 3, name:"Bonaqua",
}]
And the my.profile.items is an array with more info then above. Because it's a many-to-many relation where I stored it.
Is there even a way to do this? I don't mind a dirty solution :P
I tried this:
// NG-check function
$scope.checkStoredValues = function(my) {
// Loop trought my current stored items
angular.forEach(my.profile.items, function(value, key) {
// Loop trough the array
angular.forEach(value, function(value, key) {
console.error(key);
// If key == 'id'
if(key == 'id'){
// Push the value to the Array of the checkboxes,
// so it's checked
// # Docs: http://vitalets.github.io/checklist-model/
console.info(value); // Return's: integer
foobar.products.push(value); // Needs more then an integer I guess..
}
});
});
};
This returns: TypeError: Cannot read property 'push' of undefined
Add this function to your controller:
$scope.isChecked = function(product) {
var items = $scope.my.profile.items;
for (var i=0; i < items.length; i++) {
if (product.id == items[i].id)
return true;
}
return false;
};
And for your html use
<section ng-repeat="product in products">
<label>
<input type="checkbox"
ng-checked="isChecked(product)">
{{ product.name }}
</label>
<div ng-repeat="current_item in my.profile.items">
<span ng-show="product.id==current_item.id">Added</span>
</div>
</section>
Another trick that you can do is create a view model specific for your need.
So for example you have the array of products and items within the profile.
In the controller, you could do something like:
$scope.products.forEach(function(p){
var items = $scope.my.profile.items;
var wasAdded = false;
for (var i=0; i < items.length; i++) {
if (product.id == items[i].id)
wasAdded = true;
i = items.length;
}
$scope.productsViewModels.push({product:p, added:wasAdded});
});
Now that you have created your data as needed in the view, then you can simple do:
<section ng-repeat="p in productsViewModels">
<label>
<input type="checkbox"
ng-checked = "p.added"
checklist-model="foobar.products"
checklist-value="p.product.id">
{{ p.product.name }}
</label>
<span ng-show="p.added">Added</span>
</section>
When I have combined data from multiple sources I prefer to process it before in the controller and create a viewModel that will makes my life easy in the view.
you need to implement something like this AngularJs ng-checked using a function
you just need to implement your own logic based on your many to many product array and return true or false accordingly.
Add this after the checkbox
<span ng-if="my.profile.items.indexOf(product) >= 0">added</span>
and remove the div.

How to update view when scope does not change

there is a textarea to input. And two buttons to click.
`<textarea ng-bind="msg"></textarea>
<div class="cancel-btn" ng-click="is_hide=false;"></div>
<div class="submit-btn" ng-click="submit_card()"></div>`
What is the process ?
input some characters in textarea, then click 'submit-btn', scope will save the 'msg'. But if click 'cancel-btn', scope won't save the msg.
What do I want ?
After I save msg, then input and click 'cancel-btn', I want to the textarea's innerHTML is scope.msg instead of input.
#
Now, I want to exec $scope.$apply() to update dom, but alert error $digest already in progress.
#
Maybe I need ng-model the textarea, and use the other param saved_msgsave the msg, when click 'cancel-btn', make the msg = saved_msg. But, there are some convenient ways ?
-------------------update 6/25-----------------
`<div ng-controller="Ctrl">
<div ng-class="{'selted':cid == 0}" data-cat="0"></div>
<div ng-class="{'selted':cid == 1}" data-cat="1"></div>
<div ng-class="{'selted':cid == 2}" data-cat="2"></div>
<textarea ng-bind="msg"></textarea>
<div class="cancel-btn" ng-click="is_hide=false;"></div>
<div class="submit-btn" ng-click="submit_card()"></div>
</div>
In fact, I use some directive byjQuery` to change the dom class, when click 'cancel-btn', I want to refresh the dom decide to scope.
It may be similar to $render.
--------------------update 6/26--------------
Thank for your help. I find a way to solve the problem.I add a div#main to contain what I want to save/refresh. ps: care for some change!
The follow is my way. Maybe you have better way, welcome to share!
`<div ng-controller="Ctrl">
<div id="main">
<div ng-class="{'selted':cid == 0}" data-cat="0" selt></div>
<div ng-class="{'selted':cid == 1}" data-cat="1" selt></div>
<div ng-class="{'selted':cid == 2}" data-cat="2" selt></div>
<textarea ng-bind="msg" id="msg"></textarea>
</div>
<div class="cancel-btn" ng-click="reset()"></div>
<div class="submit-btn" ng-click="submit_card()"></div>
</div>`
In controller,
`app.controller('Ctrl', ['$scope', '$compile', function($scope, $compile) {
$scope.reset = function(){ //reset depend on scope
$('#main div').removeClass('selted');
//the dom maybe change, need clear 'selted' class first
//compile(dom)(scope)
$('#main').html($compile($('#main').html())($scope));
}
$scope.save = function(){ //save the scope
$scope.msg = $('#msg').val();
$scope.cid = $('.selted').index()+1;
}
}]);
app.directive('selt', function(){
//this is directive, only change class on dom
return function(scope,ele){
ele.click(function(){
ele.addClass('selted').siblings().removeClass('selted');
})
}
});
`
1 - You need to replace ng-bind of your textarea with ng-model, to update the scope while you are typing on it.
2 - To provide a cancel like that, in a simple manner, replace the ng-click content with a function, like cancel(formName). In your controller:
angular.module('exampleApp', [])
.controller('Ctrl', ['$scope', function ($scope) {
$scope.is_hide = false;
$scope.msg = null;
$scope.cancel = function cancel (form) {
$scope.is_hide = true;
$scope.msg = null;
if (form) {
form.$setPristine();
}
};
}]);
The formName argument is the name of the wrapper form element that you're using on your HTML, to access some specific properties, like $setPristine, which will reset the form processing to its initial state. NOTE: This doesn't mean that the fields values will be reseted.

Dynamically add input fields with Angular and perform validation on blur

Form at start has 1 input field for address. Bellow there is a link that when clicked adds another input and so on. There is no limitation in number of fields.
Is there more elegant way of adding new elements in model collection in Angular? I'm currently using an array of null elements, and on the link click I just push another null in that array so ng-repeat picks it up. And when I want to submit form I go through that array and filter out elements that are not null.
When input field is focused out/blurred there should be validation performed. I'm currently calling a function from controller on ng-blur event but I'm having trouble passing it current input text value.
Fiddle
HTML:
<div ng-app="TestApp">
<div ng-controller="MainCtrl">
<div ng-repeat="field in fields track by $index">
<input type="text" placeholder="enter the address" ng-model="fields[$index]" ng-blur="validate()" />
</div>
+ Add another input
<br/>
<br/>
List addresses
</div>
</div>
JS:
var app = angular.module("TestApp", []);
app.controller("MainCtrl", function($scope){
$scope.fields = [null];
$scope.addresses = [];
$scope.addField = function(){
$scope.fields.push(null);
};
$scope.listAddresses = function(){
for(var i in $scope.fields){
if($scope.fields[i] !== null){
$scope.addresses[i] = $scope.fields[i];
}
}
alert("Addresses: " + $scope.addresses);
};
$scope.validate = function(){
console.log("Should validate current input");
};
});
Instead of using two arrays, use one and store objects:
$scope.items =
[
{ address: '' }
];
It will now be clearer what the model of the input is, as you don't have to use $index. You can also pass the item to the validate function:
<div ng-repeat="item in items">
<input type="text" ng-model="item.address" ng-blur="validate(item)" placeholder="enter the address" />
</div>
Adding item:
$scope.addItem = function() {
$scope.items.push({ address: '' });
};
Demo: http://plnkr.co/edit/sPlPO2DfrgNHf5AasYlH?p=preview

Resources