ng-class not working inside directive for form validation - angularjs

I have a form and inside it a directive like this:
addEditUser.html:
<form role="form" class="form-horizontal" novalidate name="addEditUserForm" >
<div class="form-group labelInputContainer">
<label for="emailAddr" class="col-xs-1 col-sm-1 col-md-1 control-label">E-mail</label>
<div class="col-xs-8 col-sm-8 col-md-8">
<input type="text" ng-class="{'invalid': addEditUserForm.emailAddr.$error.required}" maxlength="100" class="form-control" id="emailAddr" name="emailAddr" placeholder="E-mail" ng-model="user.emailAddr" required />
</div>
</div>
<address default-addr="defaultAddr" />
<button type="submit" class="btn greenButton" style="width: 100%;" ng-click="saveUser();" data-ng-disabled="addEditUserForm.$invalid">SAVE</button>
</form>
addEditUserController.js
app.controller('addEditUserController', function ($scope, $routeParams, $location, $filter, reposSvc) {
$scope.defaultAddr = {};
var id = $routeParams.id;
if (id) {
$scope.user = reposSvc.user.get({ id: id },
function (data) {
$scope.defaultAddr = data.defaultAddr;
});
});
the directive HTML (address.html):
<div>
<div class="form-group labelInputContainer">
<label for="streetAddress" class="col-xs-2 col-sm-2 col-md-2 control-label">Street</label>
<div class="col-xs-3 col-sm-3 col-md-3">
<input type="text" ng-class="{'invalid': addEditUserForm.streetAddress.$error.required}" maxlength="100" class="form-control noLimitsTextBox" id="streetAddress" name="streetAddress" ng-model="defaultAddr.streetAddress" placeholder="Street" required />
</div>
<label for="no" class="col-xs-2 col-sm-2 col-md-2 control-label">No</label>
<div class="col-xs-3 col-sm-3 col-md-3">
<input type="text" maxlength="6" class="form-control" id="no" name="no" placeholder="No" />
</div>
</div>
</div>
address.js:
(function () {
'use strict';
app.directive('address', function () {
return {
restrict: 'E',
replace: true,
templateUrl: 'app/directives/address.html',
scope: {
defaultAddr: '='
},
controller: function ($scope, reposSvc, toastr) {
}
};
});
})();
The button is disabled until I fill in all the required fields (in this short example the email and the street). But only the email gets the class "invalid" applied (a background color). So mainly the validation works for both controls, but the ng-class only gets applied on the email field that is in the main html.
What am I doing wrong? how can I get the "invalid" class from ng-class applied on the directive controls too?

The reason is because your directive is isolated scoped (it does not automatically inherit from the parent), so it does not get the special form object added as property on the scope with the name of the property. So you would need to pass that as 2-way binding, or use $parent.addEditUserForm to access it which seems bad though (impact on reusability), or just use scope:true but it may not serve the purpose of your intention of using isolated scope. Validation works because your ng-model is on 2-way bound property ng-model="defaultAddr.streetAddress" which is still attached to the formcontroller instance.
So you could do:
return {
restrict: 'E',
replace: true,
templateUrl: 'app/directives/address.html',
scope: {
defaultAddr: '=',
form:'=' //<-- Add a binding
},
controller: function ($scope, reposSvc, toastr) {
}
};
and
<input type="text" ng-class="{'invalid': form.streetAddress.$error.required}" maxlength="100" class="form-control noLimitsTextBox" id="streetAddress" name="streetAddress" ng-model="defaultAddr.streetAddress" placeholder="Street" required />
and pass it in as:
<address default-addr="defaultAddr" form="addEditUserForm" />
Note: i have used the name as form in the 2-way binding so that it is not tightly coupled with a specific form name and is more reusable.
Or as other options instead of setting class using ng-class just use the ng-invalid, ng-invalid-required etc.. classes added on the element when it is invalid.

Related

Turkish characters saved to database in incorrect format when using Angular textarea control

I noticed that Turkish characters get saved differently, based on the specific HTML element control being used (correctly for textarea, incorrectly for input).
I am using the following characters for my test: ÖŞĞÜİÇ öşğüıç
Below is my HTML for the input and textarea controls (nothing fancy):
<div class="col-md-12">
<div class="row defMarginBottom">
<div class="col-md-6">
<label>Mesaj Konusu</label>
<input type="text" class="form-control" placeholder="Konu"
ng-model="titleText" />
</div>
</div>
<div class="row defMarginBottom">
<div class="col-md-3">
<label>İnternet Adresi Konusu</label>
<input type="text" class="form-control"
placeholder="İnternet Adresi Konusu"
ng-model="additionalDataSubject" />
</div>
<div class="col-md-3 defMarginLeft1_3">
<label>İnternet Adresi Linki</label>
<input type="text" class="form-control"
placeholder="İnternet Adresi Linki"
ng-model="additionalDataLink" />
</div>
</div>
<div class="row defMarginBottom">
<div class="col-md-6">
<label>Mesaj</label>
<textarea class="form-control defHeight"
placeholder="Mesaj" ng-model="message">
</textarea>
</div>
</div>
<div class="row">
<input style="margin-left:1.5%;" type="button"
class="btn btn-success" value="Kaydet ve Gönder"
ng-click="saveAndSend()" />
</div>
</div>
Following is the Angular Controller code where the ng-models are defined:
var jsonObj = {
"Subject": $scope.additionalDataSubject,
"Link": $scope.additionalDataLink
};
var obj = {
"ActivityTypeIds": activityTypeIdList,
"SalesPersonID": $scope.salesPersonId,
"SalesPersons": $scope.spIdList,
"CustomerCategories": categoryIdList,
"customerName": $scope.customerName,
"StartDate": $scope.startDate,
"EndDate": $scope.endDate,
"TitleText": $scope.titleText,
"Message": $scope.message,
"AdditionalData": JSON.stringify(jsonObj)
};
When I enter the test strings, only those for the textarea controls get saved properly to the database.
Those for input controls are saved incorrectly as: ÖŞĞÜİÇ öşğüıç
Note: I recently added the following new directive to my Angular app as a protection agaist XSS attacks, and was suspicious of it as causing the problem, but since the entries for textarea are saved correctly, I'm not sure if it is related.
.directive('input',['$sanitize', function ($sanitize) {
return {
restrict: 'E',
require: '?ngModel',
link: function (scope, element, attrs, ngModel) {
if (ngModel !== undefined && ngModel != null) {
ngModel.$parsers.push(function (value) {
return $sanitize(value);
});
}
}
};
}]);
How can I overcome this problem? Most of the questions here on SO are about rendering of Turkish characters, not about saving them.

Plug a user directive to form.$valid

I have a simple directive:
myDirective.html:
<div class="my-directive" ng-class="{'on':status,'off':!status}" ng-click="onClick()">
click me
</div>
myDirective.js:
app.directive('myDirective', function () {
return {
restrict: 'E',
templateUrl: 'myDirective.html',
controller: ['$scope', function ($scope) {
$scope.onClick = function () {
$scope.status = !$scope.status;
}
}]
}
});
I have a ng-form and I'm using this directive as a field of the form:
<ng-form name="frm">
<div class="form-field">
<input type="text" required ng-model="v1" />
</div>
<div class="form-field">
<my-directive status="v2" />
</div>
<input type="button" ng-disabled="!frm.$valid" value="save" />
</ng-form>
Live example in: JsFiddle.
When I set some text in the input frm.$valid turn to true automatically.
I want to apply to my-directive the same effect. Lets say...
Consider my-directive as valid only when status is true.
And if my-directive is invalid the form should be invalid too.
Can I set this effect in my-directive? And how can I do it?

Angular transcluded directive - can't access a model thats inside of it

This is my directive code:
'use strict';
demo.directive('myModal', function($parse) {
return {
restrict: 'A',
transclude: true,
scope: '#',
template: '<div ng-transclude><h4>Please enter value</h4></div>'
}
});
Usage is as follows:
<!-- myModal directive -->
<div my-modal>
<input type="text" ng-model="myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>
And my main controller, which wraps the whole application, includes this:
demo.controller('MainCtrl', function($scope) {
$scope.getMyTest = function(){
alert($scope.myTest);
}
});
Any ideas why can't I access myTest?
JsFiddle: http://jsfiddle.net/sZZEt/679/
You should use the dot-notation:
demo.controller('MainCtrl', function($scope) {
$scope.data = {};
$scope.getMyTest = function(){
alert($scope.data.myTest);
}
});
and
<div my-modal>
<input type="text" ng-model="data.myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>
JSFIDDLE
Transclusion creates a child scope, that's why you should use the dot-notation for ng-model.
try this. add directive to model element.
<div>
<input my-modal type="text" ng-model="myTest" />
<input type="button" ng-click="getMyTest()" value="Get Value" />
</div>

Angular JS pass attributes to directive template

I have custom directive for datepicker. I want to reuse it in several different places. But in order to reuse current directive I have to dynamically pass and change different attributes into my-datepicker directive.
If you look inside datepicker.html I am using following attributes: ng-model="departureDate" min-date="minDateDeparture" is-open="departureOpened".
Question: How do I set this attributes on the my-datepicker element level and pass all the way down to my directive html template? I want to achieve something like that:
<my-datepicker ng-model="departureDate1" min-date="minDateDeparture1" is-open="departureOpened1"></my-datepicker>
<my-datepicker ng-model="departureDate2" min-date="minDateDeparture2" is-open="departureOpened2"></my-datepicker>
Thanks for any help!
datepicker-contoller.js
app.directive('myDatepicker', function() {
return {
restrict: 'E',
templateUrl: 'templates/datepicker/datepicker.html'
};
});
datepicker.html
<fieldset>
<div class='input-group'>
<input type="text" class="form-control" datepicker-popup ng-model="departureDate" min-date="minDateDeparture" is-open="departureOpened" datepicker-options="dateOptions" ng-required="true" close-text="Close" />
<span ng-click="open1($event)" class="btn btn-default input-group-addon">
<span class="glyphicon glyphicon-calendar"></span>
</span>
</div>
</fieldset>
Datepicker usage
<div ng-controller="MyController">
<div class="col-md-2">
<my-datepicker></my-datepicker>
</div>
<div class="col-md-2">
<my-datepicker></my-datepicker>
</div>
</div>
Update: See this jsFiddle: http://jsfiddle.net/j31ky7c2/
You can pass the data as well as functions as attribute in your directive.
<my-datepicker min-date="minDateDeparture2" is-open="departureOpened2" some-function="testFunction()"></my-datepicker>
You can receive this data in your directive's scope.
directive('myDatepicker', [function() {
return {
restrict: 'E',
scope: {
minDate: '#',
isOpen: '#',
someFunction: '&'
},
link: function(scope, elm, attrs) {
}
}
}]);
Then you can simply use minDate and isOpen and someFunction in your directive template like:
<div ng-bind="{{::minDate}}"></div>
<div ng-bind="{{::isOpen}}"></div>
<Button ng-click="someFunction()">Click me</Button>

How to dynamically add ng-repeat to an element inside an angular directive?

I want to add 'ng-repeat="n in counter"' to the 'form' tag inside my directive. How do I do this?
I tried accessing the element via compile but tElement.find('form') does not work.
See : http://jsfiddle.net/fea40v2c/1/
I tried all these variations:
console.log(tElement.find('form')); // fails
console.log(tElement[0].querySelector('form')); // null
console.log(document.querySelector('form')); // fails
Do you really need the add button to be defined by the directive's user? Because if you don't you can do this.
<script id="repeatableForm.html" type="text/ng-template">
<input type="button" value="add" ng-click="add()">
<div ng-repeat="c in counter">
<div ng-transclude></div>
</div>
</script>
Update
After a little work I got something that allows the user to provide their own markup for the add button. It a bit more complicated and involves a nested directive. A few points that are good to know:
The repeatableForm directive has no isolated scope. It modifies the host scope by adding/overwriting the repeatableForm property. This means multiple such directives cannot execute in the same host scope.
The repeatableForm publishes its controller in its host scope as the repeatableForm property. This is better than publishing the controller's methods directly in the scope because it namespaces those methods and leaves the host scope cleaner.
The view
<repeatable-form>
<input type="button" value="add" ng-click="repeatableForm.add()"/>
<form action="">
First Name: <input name="fname" type="text" />
Last Name: <input name="lname" type="text" />
<input type="checkbox" name="food" value="Steak"/> Steak
<input type="checkbox" name="food" value="Egg"/> Egg
<input type="button" value="remove" ng-click="repeatableForm.remove($index)" />
</form>
</repeatable-form>
The directives
app.directive('repeatableForm', function () {
return {
templateUrl:'repeatableForm.html',
restrict: 'E',
transclude: true,
controller: function () {
var repeatableForm = this
repeatableForm.add = function () {
repeatableForm.forms.push(repeatableForm.forms.length + 1);
};
repeatableForm.remove = function (index) {
repeatableForm.forms.splice(index, 1);
};
repeatableForm.forms = [1, 2, 3];
},
controllerAs: 'repeatableForm',
};
});
app.directive('form', function () {
return {
templateUrl: 'repeatedForm.html',
restrict: 'E',
transclude: true,
};
})
The templates
<script id="repeatableForm.html" type="text/ng-template">
<div ng-transclude></div>
</script>
<script id="repeatedForm.html" type="text/ng-template">
<div ng-repeat="form in repeatableForm.forms"><div ng-transclude></div></div>
</script>
Check this demo.

Resources