Cleaning up validation logic - angularjs

I've just got validation working the way I want with the following code:
<div class="control-group" ng-class="{ error : (submitted || accountForm.name.$dirty) && accountForm.name.$invalid }">
<label for="name" class="control-label">Company Name:</label>
<input type="text" name="name" ng-model="account.name" ng-maxlength="50" required />
<span class="help-inline" ng-show="(submitted || accountForm.name.$dirty) && accountForm.name.$error.maxlength">Name too long</span>
<span class="help-inline" ng-show="(submitted || accountForm.name.$dirty) && accountForm.name.$error.required">Required</span>
</div>
But there seems to be a lot of similar code with only slight differences. What would be the best method of simplifying this to make it a) clearer, b) more maintainable?
Update 23/07 Doesn't seem like there's an immediate best practice.

You could make an error variable in the $scope of the controller that controls that template. Something like this:
html
<div ng-controller="FormCtrl" class="control-group" ng-class="{ error : (submitted || accountForm.name.$dirty) && accountForm.name.$invalid }">
<label for="name" class="control-label">Company Name:</label>
<input type="text" name="name" ng-model="account.name" ng-maxlength="50" required />
<input type="button" ng-click="submitForm()" />
<span class="help-inline">{{ error }}</span>
</div>
controller
window.FormCtrl = function ($scope) {
$scope.error = "";
$scope.submitForm = function() {
if ($scope.accountForm.name.length > 50) {
$scope.error = "Name too long";
}
else if ($scope.accountForm.name == null || $scope.accountForm.name == undefined) {
$scope.error = "Name required";
}
else {
$scope.error = "";
//submit logic
}
}
}

Since you want to reuse the same code for other form fields, and the validation logic seems to be similar for each of them, i could not find anything with ngForm that would help reuse the code. Rather I would suggest a different approach, where you don't use ngForm and do the validation in js yourself. This way you can reuse the method everywhere.
Here is the link for the jsFiddle:
http://jsfiddle.net/rCDg8/1/
<div ng-app>
<div ng-controller="DemoCtrl">
<form name="accountForm" ng-submit="submit()">
<div class="control-group" ng-class="class">
<label for="name" class="control-label">Company Name:</label>
<input type="text" name="name" ng-model="account.name" ng-change="validateEntry(account.name)" required></input>
<span class="help-inline" ng-show="accountForm.$dirty||accountForm.$invalid">{{msg}}</span>
</div>
</form>
</div>
</div>
Controller:
function DemoCtrl($scope) {
var longName = 'Name too long';
var nameRequired = 'Name is required!';
$scope.class = '';
$scope.showMsg = false;
$scope.validateEntry = function (validateItem) {
if (angular.isDefined(validateItem)) {
if (validateItem.length > 50) {
showErrorMsg(longName);
} else if (validateItem.length === 0) {
showErrorMsg(nameRequired);
} else {
$scope.class = '';
$scope.showMsg = false;
$scope.accountForm.$dirty = false;
$scope.accountForm.$pristine = true;
}
} else {
showErrorMsg(nameRequired);
}
};
$scope.submit = function(){
alert('IS Form Dirty: '+$scope.accountForm.$dirty);
};
function showErrorMsg(msg) {
$scope.class = 'error';
$scope.showMsg = true;
$scope.msg = msg;
$scope.accountForm.$dirty = true;
}
};

Related

How to bind dynamic element to DOM in angularjs 1.6?

I am creating a dynamic report builder which can have multiple rows. These rows are created dynamically based on the field selected. I am appending the dynamic HTML through my js controller but ng-model is not binded to the DOM. I tried adding $compile(element)($scope) but it didnt work. Please find the screenshot shared below.
HTML
<div class="page-content-wrap">
<form name="reportform" class="reportBuilder" ng-submit="getReport(reportform,report)">
<div class="row">
<div class="form-group col-md-4">
<select class="form-control" name="module" id="module"
ng-model="modules"
ng-options="module as module.formname for module in allModules track by module.id"
ng-change="getFields()">
<option value="" class="">Select Module</option>
</select>
</div>
</div>
<div class="dynamic_block">
<div class="row">
<div class="form-group">
<div class="col-md-4">
<select class="form-control field_type"
ng-model="fields[row_count]"
ng-options="moduleField.name as (moduleField.label) for moduleField in moduleFields">
<option value="" class="">Select Fields</option>
</select>
</div>
{{options}}
<div class="col-md-7 field_to_be_added">
</div>
</div>
</div>
</div>
<div class="row">
<div class="col-md-12">
<span ng-click="cloneRow()"><i class="fa fa-plus-circle"></i></span>
</div>
</div>
<div class="row">
<div class="col-md-6">
<button class="btn btn-primary" type="submit">Generate Report</button>
</div>
</div>
</form>
</div>
JavaScript
$scope.cloneRow = function() {
$scope.row_count++;
$scope.model_name = 'field_'+$scope.row_count;
var new_html = $('#dynamic_block_fixed').clone();
$('.dynamic_block:last').after(new_html);
$compile(new_html)($scope);
$('.minus-icon').unbind().bind('click',function(item){
$(item.target).closest('div.dynamic_block').remove();
});
$('.field_type').unbind().bind('change',function(item){
var item_id = $(this).val().split(':');
for(fields in $scope.moduleFields){
if(typeof $scope.moduleFields[fields] != 'undefined') {
if($scope.moduleFields[fields]['name'] == item_id[1]){
createFieldOptions($scope.moduleFields[fields],item.target.parentElement);
}
}
}
});
}
function createFieldOptions(field, target) {
for (f in field) {
var name = field['name'];
if (field[f] == 'text') {
var element = `<input class="form-control" dynamic type="text" ng-model="report.${name}"/>`;
} else if (field[f] == 'textarea') {
var element = `<textarea class="form-control" ng-model="report.${name}"></textarea>`;
} else if (field[f] == 'select') {
var options = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'select' && objectValues['name'] == name) {
options = objectValues['values'];
}
}
var element = `<select class="form-control" ng-model="report.${name}"><option>Select</option>`;
options.forEach(function(object) {
element += `<option value="${object.value}">${object.label}</option>`;
});
element += "</select>";
} else if (field[f] == 'checkbox-group') {
var checkoptions = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'checkbox-group' && objectValues['name'] == name) {
checkoptions = objectValues['values'];
}
}
var element = "<span>";
checkoptions.forEach(function(object) {
element += `<label class="check"><input type="checkbox" name="${name}" ng-model="report.${name}" value="${object.value}"/>${object.label}</label>`;
});
element += "</span>";
} else if (field[f] == 'date') {
element = `<input type="date" name="${name}" ng-model="report.${name}"/>`;
} else if (field[f] == 'radio-group') {
var radiooptions = [];
for (key in $scope.moduleFields) {
var objectValues = $scope.moduleFields[key];
if (objectValues['type'] == 'radio-group' && objectValues['name'] == name) {
radiooptions = objectValues['values'];
}
}
var element = "<span>";
radiooptions.forEach(function(object) {
element += `<label class="check"><input type="radio" name="${name}" ng-model="report.${name}" value="${object.value}"/>${object.label}</label>`;
});
element += "</span>";
}
}
angular.element(target).next('div.col-md-7').html(element);
//$(target).next('div.col-md-7').html(element);
$compile(element)($scope);
$scope.$apply();
}
Open to alternative approach if any.
It sounds like you're appending the dynamic HTML in the JQuery style, with parent.append(newHTML) kinda thing; That's not great practice in AngularJS, and also when you have to use $compile, you know something's not quite right.
I would recommend structuring your code in directives and showing and hiding each directive via ng-if.
If you give some HTML examples, I could go more in depth

Adding Angular validation to #Html.EditorFor

Is it possible to add angular validation for #Html.EditorFor. Please see code below.
--> This is how I get the partial view from the controller
$scope.editClient = function () {
var id = 1;
$http.get('/Clients/GetClientByClientId?id=' +id)
.then(function (response) {
debugger;
var divTemplate = response.data;
var element = angular.element(document.getElementById('id_chosen_view'));
element.empty();
element.append(divTemplate);
$compile(element)($scope);
})
}
<div class="form-group">
<label for="editCompany">Company</label>
#Html.EditorFor(model => model.Company, new { htmlAttributes = new { #class = "form-control", required="required", id="editCompany" } })
<span style="color: red;" ng-show="formEditClient.Company.$touched && formEditClient.Company.$invalid">Company name is required.</span>
</div>
but this works
<label for="inputContactPerson">Contact Person</label>
<input id="inputContactPerson" type="text"
class="form-control" ng-model="editClient.ContactPerson" name="ContactPerson" required/>
<span style="color: red;" ng-show="formEditClient.ContactPerson.$touched && formEditClient.ContactPerson.$invalid">Contact Person is required.</span>
I would like to use the first code but it doesn't work.

How can I do event on text box in Angular?

I have a text box that show when I click on checkbox and I need to make an event on it that can make me bind it to object from DB
HTML:
<div ng-repeat="feture in featureEnumArray">
<input id="txtchkFet" type="checkbox" ng-model="feture.IsChecked" />
{{feture.DisplayText}}
<div ng-show="feture.IsChecked" >
<input class="form-control" type="text" ng-model="feture.Value" ng-change="getFeatureID(feture)" id="txtValue" placeholder="Type Feature Value" />
</div>
<div class="label label-danger" ng-show="feture.IsChecked && !feture.Value">
Type Value
</div>
</div>
And in Angular controller I did Like this :
$scope.getFeatureID = function (feture) {
if (feture.IsChecked) {
$scope.Planfeature = angular.copy(feture);
$scope.PlanFeatureTemp.FeatureID = $scope.Planfeature.ID;
$scope.PlanFeatureTemp.Value = $scope.Planfeature.Value;
$scope.Planfeature = [];
}
else {
var index = $scope.JAdminPlan.PlanFeatureValues.indexOf(feture);
$scope.JAdminPlan.PlanFeatureValues.splice(index, 1);
}
if (!$scope.$$phase) $scope.$apply();
};

Disable and empty model value and then restore it

I have working code:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled">
When disabled == true the input is disabled. I need to hide actual model value (set empty) as well. And after uncheck the checkbox actual model value should appears. How to do it without changing model value?
I would do something like this:
<input type="checkbox" ng-model="disabled" ng-change="change()">
<input type="text" ng-model="text" ng-disabled="disabled">
...
$scope.change = function() {
if ($scope.disabled) {
$scope.textBackup = $scope.text;
$scope.text = '';
} else {
$scope.text = $scope.textBackup;
}
};
Note: I posted before reading #AbdulMateenMohammed comment... My answer is an implementation of his suggestion...
This is not the best option but if you want to deal only with the view:
<input type="checkbox" ng-model="disabled">
<div ng-show="disabled">
<input type="text" ng-init="text2 = ''" ng-model="text2" ng-disabled="true">
</div>
<div ng-hide="disabled">
<input type="text" ng-model="text">
</div>
Again, this is not the best option!!! MarcosS option is the recommended.
#Makarov Sergey, I think even when you have a complex view or data source the basic idea is to use a temporary variable because you need to have two values b/w which you swap around.
angular
.module('demo', [])
.controller('DefaultController', DefaultController);
function DefaultController() {
var originalText = '';
var vm = this;
vm.text = 'Hello, World!';
vm.onValueChanged = onValueChanged;
function onValueChanged(text) {
if (vm.disabled) {
originalText = text;
vm.text = '';
} else {
vm.text = originalText;
}
}
}
span {
color: red;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div>
<em>I </em><span>♥ </span><em>AngularJS</em>
</div>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<input type="checkbox" ng-model="ctrl.disabled" ng-change="ctrl.onValueChanged(ctrl.text)"/>
<input type="text" ng-model="ctrl.text" ng-disabled="ctrl.disabled"/>
</div>
</div>
Tip: For the objects that aren't displayed in the view but present in the controller shouldn't be assigned on the scope because using scope would add some JavaScript events to do the data-binding which is unnecessary overhead such as the dirty check event for the two-way data-binding so that's why in the sample code snippet I used var originalText = ''; instead of vm.originalText = '';
Found the solution that works for me atm
function directive() {
return {
restrict: 'A',
require: 'ngModel',
link: function (scope, element, attrs, ngModelCtrl) {
let oldViewValue = '';
scope.$watch(() => attrs.hideValue, (newV) => {
if (newV == 'true') {
oldViewValue = ngModelCtrl.$viewValue;
ngModelCtrl.$viewValue = '';
} else {
ngModelCtrl.$viewValue = oldViewValue;
}
ngModelCtrl.$render();
});
}
}
}
And use:
<input type="checkbox" ng-model="disabled" >
<input type="text" ng-model="text" ng-disabled="disabled" hide-value="disabled">

AngularJs Expression not working

I have a weird issue with the {{json_req}} expression, the {{pseudo}} and {{password}} expression are working well, I can see the changes I make in live.
But nothing happened for the {{json_req}} expression, no matter what I write on the Login and Password input.
I guess It's a basic mistake by me but I'm a little lost with this one right now :/.
Thanks for the help :)
Login.html
<div class="row">
Pseudo : {{pseudo}}
</div>
<div class="row">
Password : {{password}}
</div>
<div class="row">
json : {{json_req}}
</div>
<div class="row">
<label class="float-center">
Pseudo
<input ng-model="pseudo" type="text" required>
<span class="form-error">
Pseudo Missing.
</span>
</label>
</div>
<div class="row">
<label class="float-center">
Password
<input ng-model="password" type="password" required>
<span class="form-error">
Password Missing.
</span>
</label>
</div>
LoginCtrl.js
mCtrl.controller('LoginCtrl', ['$scope', 'User', function ($scope, User) {
$scope.json_req = {
pseudo: $scope.pseudo,
password: $scope.password
};
$scope.LoginUser = function () {
if ($scope.json_req.pseudo != undefined && $scope.json_req.password != undefined) {
User.login($scope.json_req).then(function (data) {
$scope.response = data;
$scope.json_req = {};
});
} else
console.log("UNDEFINED");
};
}]);
This is expected behaviour. When you write:
$scope.json_req = {
pseudo: $scope.pseudo,
password: $scope.password
};
you create a "snapshot" of the values for $scope.pseudo and $scope.password. They will not update when you change model later.
You could setup a $scope.$watch and update json_req when either of pseudo or password changes (not really recommended). Or what I would recommend, write a getter function on the scope:
Object.defineProperty($scope, 'json_req', {
get: function() {
return {
pseudo: $scope.pseudo,
password: $scope.password
}
}
});
Try this:
angular.module("asdfapp",[]).service("User",function(){}).controller('LoginCtrl', ['$scope', 'User',
function ($scope, User) {
$scope.json_req = {
pseudo: "",
password: ""
};
$scope.LoginUser = function () {
if ($scope.json_req.pseudo != undefined
&& $scope.json_req.password != undefined) {
User.login($scope.json_req).then(function (data) {
$scope.response = data;
$scope.json_req = {};
});
} else
console.log("UNDEFINED");
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="asdfapp" ng-controller="LoginCtrl">
<div class="row">
Pseudo : {{json_req.pseudo}}
</div>
<div class="row">
Password : {{json_req.password}}
</div>
<div class="row">
json : {{json_req}}
</div>
<div class="row">
<label class="float-center">
Pseudo
<input ng-model="json_req.pseudo" type="text" required>
<span class="form-error">
Pseudo Missing.
</span>
</label>
</div>
<div class="row">
<label class="float-center">
Password
<input ng-model="json_req.password" type="password" required>
<span class="form-error">
Password Missing.
</span>
</label>
</div>
</div>
Ok I find the solution. And is expected It was a stupid mistake from me xD
I should write ng-model="json_req.pseudo" and not ng-model="pseudo" to fill the json_req object :/
Sorry to have bothered you guys !
Thanks

Resources