AngularJS Directive - Template replacements inside ng-repeat - angularjs

I have the following directive code (AngularJS 1.4.8). The issue I have is using template replacements inside the ng-repeat.
(function() {
'use strict';
angular.module('app').directive('formGroup', formGroup);
var template = '<div class="form-group">' +
'<label class="control-label">{{label}}</label>' +
'<div data-ng-transclude></div>' +
'<span class="text-danger field-validation-error" data-ng-repeat="errorMessage in {{form}}.$error.{{model}}.messages">' +
'{{errorMessage}}' +
'</span>' +
'</div>';
function formGroup () {
return {
restrict: 'A',
require: '^form',
transclude: true,
replace: true,
scope: {
label: '#',
model: '#'
},
template: template,
link: link
};
}
function link(scope, element, attrs, ctrl) {
scope.form = element.closest('form').attr('name');
}
})();
It is used in HTML in the following way (server-validate is another directive that puts in-line validation errors returned from the server into the ng-repeat in the directive).
<form name="myForm" data-ng-submit="submit()">
<div class="col-lg-4 col-md-6">
<div data-form-group data-label="Memeber number" data-model="memberNumber">
<input type="text"
class="form-control"
data-ng-model="model.memberNumber"
data-server-validate />
</div>
</form>
I want it to produce the following output HTML.
<form name="myForm" data-ng-submit="submit()">
<div class="form-group">
<label class="control-label">Member number</label>
<div>
<input type="text"
class="form-control"
data-ng-model="model.memberNumber"
data-server-validate" />
<span class="text-danger field-validation-error"
data-ng-repeat="errorMessage in myForm.$error.memberNumber.messages">
{{errorMessage}}
</span>
</div>
</div>
</form>
However, it gets the following error.
[Error] [$parse:syntax] Syntax Error: Token '{' invalid key at column 2 of the expression [{{form}}.$error.{{model}}.messages] starting at [{form}}.$error.{{model}}.messages]

Since form controller is injected as required, using the formcontroller, the error messages can be handled like below.
function link(scope, element, attrs, ctrl) {
var unbinder = scope.$watch(function () {return ctrl[scope.model] }, function(value) {
if (!value) return;
scope.errorMessagesList = ctrl[scope.model].$error;
unbinder();
})
}
The complete working sample is availabe in the fiddle:
http://jsfiddle.net/acb3c8n7/5/

In the link function use:
function link(scope, element, attrs, ctrl) {
scope.formString = element.closest('form').attr('name');
scope.form = scope.$eval(scope.formString);
}
In the template use:
ng-repeat="errorMessage in form.$error[model].messages"
The $eval method evaluates the string as an Angular expression and will return a reference to the myForm object.

closest() is not present in the elementargument (check this).
Also you have several errors, by example you don't provide a name attribute in the input, this causes that the scope.myForm object don't recognize the model string provided how a property.
<input type="text" name="memberNumber"
class="form-control"
data-ng-model="model.memberNumber"
data-server-validate" />
In your ng-repeat you shouldn't provide a expression like this:
{{form}}.$error.{{model}}.messages
you should be create a property to attach the form object and the use some like this --> http://codepen.io/gpincheiraa/pen/eZGZPX

Related

AngularJS: Linking multiple scope variables inside external template

I am trying to build some custom directives for inputs with validations provided by ngMessages directive. Still, I can't link multiple variables from $scope to dynamically determine the form name and the input name. Here's my code so far:
The directive:
app.directive('textBox', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
label: "#",
fieldName: "#",
bindTo: "="
},
require: "^form",
templateUrl: '/WebClient/Directives/TextBox/textBoxTemplate.html',
link: function (scope, element, attrs, ctrl) {
$scope.formName = ctrl.$name;
}
};
}]);
The template:
<div>
<label>{{label}}</label>
<input type="text" name="{{fieldName}}" ng-model="{{field}}" required />
<div ng-messages="{{formName}}.{{fieldName}}.$error">
<div ng-message="required">You left the field blank...</div>
<div ng-message="minlength">Your field is too short</div>
<div ng-message="maxlength">Your field is too long</div>
<div ng-message="email">Your field has an invalid email address</div>
</div>
</div>
The usage:
<text-box bind-to="myField" field-name="myField"></text-box>
The issues I encounter are related to the ng-messages attribute value. Doesn't seem to work when I use curly braces and it renders the text "formName.fieldName.$error" if I don't. The other issue is related to the ng-model, the same scenario applies.
Thank you!
You can pass the formController, ctrl in your link method to directive scope like scope.form = ctrl;.
Then you can add <div ng-messages="form[fieldName].$error"> to access the $error property.
Curly braces are only required for the name of the input field. For ng-model you can directly add the model with-out curlies because ng-model requires two-way binding to work.
Please have a look at the demo below or this jsfiddle.
angular.module('demoApp', ['ngMessages'])
.controller('mainController', MainController)
.directive('textBox', ['$compile', function ($compile) {
return {
restrict: 'E',
scope: {
label: "#",
fieldName: "#",
bindTo: "="
},
require: "^form",
templateUrl: '/WebClient/Directives/TextBox/textBoxTemplate.html',
link: function (scope, element, attrs, ctrl) {
//$scope.formName = ctrl.$name;
scope.form = ctrl;
}
};
}]);
function MainController($scope) {
$scope.myField = 'test';
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.5/angular-messages.js"></script>
<div ng-app="demoApp" ng-controller="mainController">
<form name="testForm">
<text-box bind-to="model" field-name="myField"></text-box>
</form>
{{model}}
<script type="text/ng-template" id="/WebClient/Directives/TextBox/textBoxTemplate.html">
<div>
<label>{{label}}</label>
<input type="text" name="{{fieldName}}" ng-model="bindTo" required />
<div ng-messages="form[fieldName].$error">
<div ng-message="required">You left the field blank...</div>
<div ng-message="minlength">Your field is too short</div>
<div ng-message="maxlength">Your field is too long</div>
<div ng-message="email">Your field has an invalid email address</div>
</div>
</div>
</script>
</div>

ngTransclude doesnt render content correctly

Trying to dynamically pass error/feedback messages to a directive through Transclude. However the transcluded content doesnt redner corrrectly either due to the tag span or the innerForm.inputField.$error.....
EDIT: text between brackets [text] is test that needs to be translated. Dont be confused by that.
Cant find info about limitations about transcluding content. Hope to get some pointers here.
The Directive tag in the DOM
<input-validation-below
place-holder="[E-mail address]"
identifier="forgotten_email"
ng-model="email"
type="email">
<span data-ng-show="innerForm.inputField.$error.required">[E-mail address] [is required]</span>
<span data-ng-show="innerForm.inputField.$error.email">[This is not a valid] [E-mail address]</span>
</input-validation-below>
The Directive
angular.module('inputValidationBelow', []).directive('inputValidationBelow', function () {
return {
scope: {
login: '=ngModel',
placeHolder: '#',
identifier: '#',
type: '#'
},
templateUrl: 'scripts/directives/inputValidationBelow/inputValidationBelow.html',
restrict: 'E',
transclude: true,
require: ['ngModel', '^form'],
link: function (scope, element, attrs, controllers) {
scope.type = attrs.type;
var ngModelController = controllers[0];
ngModelController.$render = function () {
if (typeof ngModelController.$viewValue !== 'undefined') {
scope.login = ngModelController.$viewValue;
}
};
}
};
});
The Directive template
<ng-form name="innerForm"
<label class="full-width" for="inputField" data-ng-class="{ error: innerForm.inputField.$dirty && innerForm.inputField.$invalid }">
<!--[if lt IE 9]><p>{{placeHolder}}:</p></p><![endif]-->
<input class="form full-width" type="{{type}}" name="inputField" placeholder="{{placeHolder}}" id="inputField" data-ng-model="login[identifier]" required />
</label>
<div class="alert alert-input" data-ng-show="innerForm.inputField.$dirty && innerForm.inputField.$invalid">
<span class="alert alert-input" ng-transclude></span>
</div>
</ng-form>
--EDIT--
adding this line to the Link function $compile(element.contents())(scope); "fixes" the issue but gives me an error:
Error: [ngTransclude:orphan] Illegal use of ngTransclude directive in the template! No parent directive that requires a transclusion found. Element: <span class="alert alert-input" ng-transclude="">

Get access to form controller validation errors in a custom directive

I have a directive that wraps a form element with some inputs. One of the options is passing in a formName. Usually, with a form with the example name of myForm, to show an error you would do something like myForm.firstName.$error.required.
But, how do I get access to the errors when the form name is dynamically being passed in to the directive?
example usage
<my-custom-form formName='myForm' formSubmit='parentCtrl.foo()'></my-custom-form>
directive
angular.module('example')
.directive('myCustomForm', [
function() {
return {
restrict: 'E',
replace: true,
templateUrl: 'myCustomForm.directive.html',
scope: {
fornName: '#',
formSubmit: '&'
},
require: ['myCustomForm', 'form'],
link: function(scope, element, attrs, ctrls) {
var directiveCtrl = ctrls[0];
var formCtrl = ctrls[1];
scope.data = {};
scope.hasError = function(field) {
// how do i show the errors here?
};
scope.onSubmit = function() {
scope.formSubmit();
};
}
};
}]);
template
<form name="{{ formName }}" ng-submit="onSubmit()" novalidate>
<div class="form-group" ng-class="{'is-invalid': hasError('fullName') }">
<input type="text" name="fullName" ng-model="data.full_name" required />
<div ng-show="hasError('fullName')">
<p>How do I show this error?</p>
</div>
</div>
<div class="form-group" ng-class="{'is-invalid': hasError('email') }">
<input type="text" name="email" ng-model="data.email" ng-minlength="4" required />
<div ng-show="hasError('email')">
<p>How do I show this error?</p>
</div>
</div>
<button type="submit">Submit</button>
</form>
I think the only problem with your code is that the directive requires itself, I don't think that will work. Just removing the myCustomForm from the require works fine.
To check if the field has errors, you just need to check if the $error object in the form controller is empty.
require: ['form'],
link: function(scope, element, attrs, ctrls) {
var formCtrl = ctrls[0];
scope.data = {};
scope.hasError = function(field) {
// Field has errors if $error is not an empty object
return !angular.equals({}, formCtrl[field].$error);
};
Plunker

ng-model is not bind with controller's $scope when I am creating a form dynamically using directive.

view code:- mydir is my custom directive
<div ng-model="vdmodel" mydir="dataValue">
</div>
my directive :-
app.directive('mydir',['$translate',function($translate){
return {
restrict: 'A',
transclude: true,
scope: {dir:'=mydir'},
compile: function(element, attrs) {
return function(scope, element, attrs, controller){
var setTemplate = '';
var setOpt = '';
if(scope.dir.itemtype== 'NUMBER'){
setTemplate = '<input type="number" class="form-control form-font ng-animate ng-dirty"';
setTemplate +='" ng-model="dir[somevalue]" value="'+scope.sizing.somevalue+'" >';
element.html(setTemplate);
}
}
}
}
});
There are many more form element in directive, but when I am trying to submit and collect value in my controller function I get nothing.
What I am doing wrong and what is the best way to collect form values ?
there are quiet a few changes that you will need to do
1.as you are using isolate scope, pass ngModel as well to the directive
scope: {dir:'=mydir', ngModel: '='},
2.as per the best practise ngModel must always have a dot
ng-model="params.vdmodel"
3.make sure to initialize the params object in controller
$scope.params = {}
Usually, a directive would share the same scope as the parent controller but since you are defining a scope in your directive, it sets up it's own isolate scope. Now since the controller and directive have their seperate scope, you need a way to share the data between them which is now done by using data: "=" in scope.
The app code
var myApp = angular.module('myApp', []);
myApp.controller('myController', function ($scope, $http) {
$scope.vdmodel = {};
})
.directive("mydir", function () {
return {
restrict: "A",
scope:{
data:"=model",
dir:'=mydir'
},
templateUrl: 'test/form.html'
};
});
The form.html
<form>
Name : <input type="text" ng-model="data.modelName" /><br><br>
Age : <input type="number" ng-model="data.modelAge" /><br><br>
Place : <input type="text" ng-model="data.modelPlace" /><br><br>
Gender:
<input type="radio" ng-model="data.modelGender" value="male"/>Male<br>
<input type="radio" ng-model="data.modelGender" value="female"/>Female<br><br><br>
</form>
The page.html
<div ng-app="myApp" >
<div ng-controller="myController" >
<div model="vdmodel" mydir="dataValue"></div>
<h3>Display:</h3>
<div>
<div>Name : {{myData.modelName}} </div><br>
<div>Age : {{myData.modelAge}}</div><br>
<div>Place : {{myData.modelPlace}}</div><br>
<div>Gender : {{myData.modelGender}}</div><br>
</div>
</div>
</div>
You have to use $compile service to compile a template and link with the current scope before put it into the element.
.directive('mydir', function($compile) {
return {
restrict: 'A',
transclude: true,
scope: {
dir: '=mydir'
},
link: function(scope, element, attrs, controller) {
var setTemplate = '';
var setOpt = '';
if (scope.dir.itemtype == 'NUMBER') {
setTemplate = '<input type="number" class="form-control form-font ng-animate ng-dirty"';
setTemplate += '" ng-model="dir.somevalue" value="' + scope.dir.somevalue + '" >';
element.html($compile(setTemplate)(scope));
}
}
}
});
See the plunker below for the full working example.
Plunker: http://plnkr.co/edit/7i9bYmd8blPNHch5jze4?p=preview

Angularjs: validation not working when control is based on directive

Being rather new to Angularjs, I am creating textbox-label combinations in Angularjs using directives. It's working very well, but I can't get validation to work. Here is a stripped-down example.
The Html:
<form name="form" novalidate ng-app="myapp">
<input type="text" name="myfield" ng-model="myfield" required />{{myfield}}
<span ng-show="form.myfield.$error.required">ERROR MSG WORKING</span>
<br>
<div mydirective FIELD="myfield2" />
</form>
The Javascript:
var myapp = angular.module('myapp', []);
myapp.directive('mydirective', function () {
return {
restrict: 'A',
scope: { ngModel: '=' },
template: '<input type="text" name="FIELD" ng-model="FIELD" />{{FIELD}}
<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
});
The hard coded input - myfield - works, the other - myfield2 - doesn't (the binding does, just not the required-error message).
How do I tell the ng-show attribute to sort of "replace" FIELD in form.FIELD.$error.required by myfield2?
Here is a jsFiddle.
The problem is that your directive creates a new scope for the directive, this new scope does not have access to the form object in the parent scope.
I came up with two solutions, though I suspect there is a more elegant "Angular" way to do this:
Passing down the form object
Your view becomes:
<div mydirective FIELD="myfield2" form="form" />
And the scope definition object:
return {
restrict: 'A',
scope: {
ngModel: '=',
form: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};
I've updated the fiddle with this code: http://jsfiddle.net/pTapw/4/
Using a controller
return {
restrict: 'A',
controller: function($scope){
$scope.form = $scope.$parent.form;
},
scope: {
ngModel: '='
},
template: '<input type="text" name="FIELD" ng-model="FIELD" required/>{{FIELD}}<span ng-show="form.FIELD.$error.required">ERROR MSG NOT WORKING</span>'
};

Resources