I have validation set up, and am using ngMessages to show errors. However I want to create a wrapper directive around ngMessages that adds some HTML (to avoid a lot of code duplication).
So instead of having to write this on each input:
<div class="help-block validator">
<div ng-messages="registerForm.email.$error" ng-if="registerForm.email.$touched">
<p ng-message="required">This field is required.</p>
</div>
</div>
I'd just write something like this:
<error-message messages='{"required": "This field is required."}' error="registerForm.email.$error" touched="registerForm.email.$touched"></error-message>
The issue with my directive is that error and touched come up as strings, they don't get evaluated as JS expressions. I've tried to $eval them, but that throws an error.
Here's my directive:
angular
.module('myApp')
.directive("errorMessage", function () {
return {
restrict: "E",
replace: true,
scope: {
'messages': '=',
'error': '=',
'touched': '='
},
template: '<div class="help-block validator">' +
'<div ng-messages="error" ng-if="touched">' +
'<div ng-repeat="(key, message) in messages">' +
'<p ng-message="key">{{message}}</p>' +
'</div>' +
'</div>' +
'</div>',
link: function (scope, elem, attrs) {
scope.error = attrs.error;
scope.touched = attrs.touched;
scope.messages = scope.$eval(attrs.messages); // this works
}
};
});
Any idea how I should go about doing this?
Found the issue. Looks like attrs wasn't what I needed. The properties were already in the scope. The code I ended up with is:
angular
.module('myApp')
.directive("errorMessage", function () {
return {
restrict: "E",
replace: true,
scope: {
'messages': '=',
'error': '=',
'touched': '='
},
template: '<div class="help-block validator">' +
'<div ng-messages="error" ng-if="touched">' +
'<div ng-repeat="(key, message) in messages">' +
'<p ng-message="{{key}}">{{message}}</p>' +
'</div>' +
'</div>' +
'</div>',
link: function (scope, elem, attrs) {
}
};
});
I think ng-message-include meets your requirements. we can create new html file and place all of our messages in this file and just call it with ng-messages-include.
hope my answer is usable for you.
Related
Is there a possible way to use the value of an attribute and use it to concatenate in the directive template
<text-input txt-info="textName" ng-model="pi.medRecNumber"> </text-input>
hcare.directive('textInput', function() {
return {
restrict: 'AE', //attribute or element
scope: {
bindedModel: "=ngModel", // Element Model Data
txtInfo:'#', // Serve as name and id attribute value
},
template:
'<div class="form-group tcell-220" data-ng-class="{true: \'has-error\'}[submitted && formHcare.'+txtInfo+'.$invalid]">'+
'<div class="fg-line">' +
'<input id="'+txtInfo+'" name="'+txtInfo+'" data-ng-model="bindedModel" type="text" class="input-h25 form-control" placeholder="M#####-#" >'+
'</div>'+
'<small class="help-block" data-ng-show="submitted && formHcare.'+txtInfo+'.$error.required">Field Required</small>'+
'</div>',
replace: true,
require: '?ngModel',
link: function($scope, elem){
// LOGIC HERE
}
};
});
I think I got it working..
The thing with directives is that you have the same scope as the parent so you don't really need the 'bindedModel' variable and you can approach the txtInfo attribute with {{}} as we always do in angular.
Here is your code with my fixes to make it working:
hcare.directive('textInput', function() {
return {
restrict: 'AE', //attribute or element
replace: true,
scope: {
//bindedModel: "=ngModel", // Element Model Data
txtInfo:'#', // Serve as name and id attribute value
},
template:
'<div class="form-group tcell-220" data-ng-class="{true: \'has-error\'}[submitted && formHcare.{{txtInfo}}.$invalid]">'+
'<div class="fg-line">' +
'<input id="{{txtInfo}}" name="{{txtInfo}}" data-ng-model="pi.medRecNumber" type="text" class="input-h25 form-control" placeholder="M#####-#" >'+
'</div>'+
'<small class="help-block" data-ng-show="submitted && formHcare.{{txtInfo}}.$error.required">Field Required</small>'+
'</div>',
//require: '?ngModel',
link: function($scope, elem){
// LOGIC HERE
}
};
});
don't forget to initialize the pi object in the controller for it to work:
hcare.controller('AppCtrl', function($scope) {
$scope.pi = {};
});
If you want to make sure it is working you should use the browser's inspect element option on this input field.
I'm trying to write a custom directive to replace similar buttons on my page. But when I move ng-class into directive's template, it's not working anymore. Is it wrong to include ng-class within custom directive? Should I use addClass and removeClass in link function instead?
html:
<dt-button ngclass="{'active-button': selectedRows.length >=1}" text="tablebuttons.delete" icon="v-delete" ng-click="deleteDialog()"></dt-button>
directive
.directive('dtButton', function() {
return {
restrict: 'E',
scope: {
icon: '#',
text: '#',
ngclass: '='
},
link: function(scope, ielem, iattrs) {
},
template:
'<button ng-class="{{ngclass}}">' +
'<span class="{{icon}}"></span>' +
'<p translate="{{text}}">' +
'</p>' +
'</button>'
}
})
try use this. change class to ng-class in your template.
you pass a model to directive for text in view while it is not 2 way data binding.
template:
'<button class="active-button" ng-class="{{ngclass}}">' +
'<span class="{{icon}}"></span>' +
'<p translate="{{text}}">' +
'</p>' +
'</button>'
// Code goes here
var app = angular
.module('MyApp', [])
.controller('Main', ['$scope',
function($scope) {
var vm = this;
vm.selectedRows = 4;
vm.deleteDialog = function() {
console.log(vm.selectedRows);
vm.selectedRows = 0;
}
}
])
.directive('dtButton', function() {
return {
restrict: 'E',
scope: {
icon: '#',
text: '#',
ngclass: '='
},
controller: "Main as ctrl",
link: function(scope, ielem, iattrs) {
},
template: '<button ng-class="ngclass" >' +
'<p>{{text}}</p>' +
'</button>'
}
});
.active-button {
background-color: green;
color: white;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div class="main-content" ng-app="MyApp" ng-controller="Main as ctrl">
<div>
<dt-button ngclass="{'active-button':ctrl.selectedRows >=1}" ng-click="ctrl.deleteDialog()" text="delete"></dt-button>
</div>
</div>
I think nothing wrong with your approach to put ng-class at template of directive. I have tried to reproduce your code snippet at this plunk it is give the correct class name active-button which i defined at style.css with background color blue. But because i don't know much about expression selectedRows.length >=1 on your ngclass attribute, i make it just to true value which will always give active-button class to the element. When you change it to false, it will remove the active-button class.
My guess is seem something wrong with your expression selectedRows.length >=1. At following element declaration :
<dt-button ngclass="{'active-button': selectedRows.length >=1}" text="tablebuttons.delete" icon="v-delete" ng-click="deleteDialog()"></dt-button>
Maybe you can check by bind those expression return value to the element with double curly brace or any other way.
Small correction for your code, you may need to put semicolon ( ; ) at the end of return keyword inside .directive().
Try This
jimApp = angular.module("mainApp", []);
jimApp.controller('mainCtrl', function($scope){
$scope.selectedRows = [0];
$scope.tablebuttons = {delete:"Delete"};
$scope.deleteDialog = function(){
$scope.selectedRows = [];
}
});
jimApp.directive('dtButton', function() {
return {
restrict: 'E',
scope: {
icon: '#',
text: '#',
myClass: '#'
},
link: function(scope, ielem, iattrs) {
console.log(scope.myClass);
},
template:
'<button class="{{myClass}}">' +
'<span class="{{icon}}"></span>' +
'{{text}}' +
'</button>'
}
})
.active-button{
background:red;
}
.inactive-button{
background:#ccc;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="mainApp" ng-controller="mainCtrl">
<dt-button my-class="{{selectedRows.length?'active-button':'inactive-button'}}" text="{{tablebuttons.delete}}" icon="v-delete" ng-click="deleteDialog()"></dt-button>
</div>
I want to create a directive for html like this
<div my-modal my-modal-id="test">
<div class="inner">Hello Inner</div>
</div>
want to generate html from above to something like
<div id="test">
<h1>My Heading</h1>
<div class="b">
Hello Inner
</div>
</div>
js
.directive('myModal', function() {
return {
restrict: 'A',
replace: true,
scope: {
myModalId: '#'
},
compile: function(tEle, tAttrs, transcludeFn) {
//what to do here?
//I want to get div.inner of the original html
},
template: '<div id="{{myModalId}}">' +
'<h1>My Heading</h1>' +
'<div class="b"></div>' +
'</div>'
}
});
I don't know if you can use a template, it seems to overwrite the existing html before compile is called. Grabbing the HTML and replacing it yourself seems to work (plnkr):
.directive('content', function($compile) {
var dir = {
restrict: 'E',
xemplate: '<div id="{{myModalId}}">' +
'<h1>My Heading</h1>' +
'<div class="b">Original:<br/><pre>{{original}}</pre></div>' +
'</div>',
compile: function(element, attrs, linker) {
var original = element.html(); // grab original
element.html(dir.xemplate); // set template html manually
return function(scope, element, attributes) {
scope.original = original
}
}
};
return dir;
});
I have a form based on twitter bootstrap, each field have it's own configuration
// controller (the template shows this in ng-repeat
$scope.fields = [{name:"f1", label:"Field 1", with_button: false},
{name:"f2", label:"Field 2", with_button: true}]
I'm trying to make a "conditional directive" that customize the template according to "field.with_button"
// Without button
<div class="controls">
<input type="text" id="i_{{field.name}}">
</div>
// With button
<div class="controls">
<div class="input-append">
<input type="text" id="i_{{field.name}}">
<span class="add-on">bt</span>
</div>
</div>
I searched a lot and didn't find any solution, I tried to create only one div and put contents inside with a compiler function but it didn't parse, and if I call $apply it crashes.
How could I make this directive?
wrong My last try:
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls">{{innerContent}}</div>',
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
$scope.$eval('$scope.innerContent = \'<input type="text" id="input_{{field.name}}" placeholder="{{field.name}}" class="input-xlarge">\'');
}]
};
});
//<ss-field field="{{field}}"></ss-field>
You can use the $http and $compile services to do what you're after.
http://plnkr.co/edit/Xt9khe?p=preview
This plnkr should demostrate what needs to be done, but basically:
Use $http to load the template depending on the condition.
Compile the loaded template against the current scope with $compile.
angular.module('mymodule',[]).directive('ssField', ['$http', '$compile', function($http, $compile) {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template: '<div class="controls"></div>',
link: function(scope, element, attrs) {
var template;
var withButtonTmpl = 'with_button.html';
var withoutButtonTmpl = 'without_button.html';
if (scope.field.with_button) {
$http.get(withButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
} else {
$http.get(withoutButtonTmpl).then(function(tmpl) {
template = $compile(tmpl.data)(scope);
element.append(template);
});
}
}
};
}]);
You can change the directive to be more robust so the URLs aren't directly embedded in the directive for re-usability, etc., but the concept should be similar.
Just to further expand on Cuing Vo's answer here is something similar to what I use(without using external partials and additional $http calls):
http://jsfiddle.net/LvUdQ/
myApp.directive('myDirective',['$compile', function($compile) {
return {
restrict: 'E',
template: '<hr/>',
link: function (scope, element, attrs, ngModelCtrl) {
var template = {
'templ1':'<div>Template 1</div>',
'templ2':'<div>Template 2</div>',
'default':'<div>Template Default</div>'
};
var templateObj;
if(attrs.templateName){
templateObj = $compile(template[attrs.templateName])(scope);
}else{
templateObj = $compile(template['default'])(scope);
}
element.append(templateObj);
}
};
}]);
However Im not quite sure its by the bible from performance perspective.
In AngularJS, directly manipulate the DOM must only be a last resort solution. Here, you can simply use the ngSwitch directive :
angular.module('mymodule',[]).directive('ssField', function() {
return {
transclude:false,
scope: {
field: '='
},
restrict: 'E',
replace:true,
template:
'<div class="controls" data-ng-switch="field.with_button">' +
'<input type="text" id="i_{{field.name}}" data-ng-switch-when="false">' +
'<div class="input-append" data-ng-switch-default>' +
'<input type="text" id="i_{{field.name}}">' +
'<span class="add-on">bt</span>' +
'</div>' +
'</div>',
};
});
I'm attempting to build a new directive on top of an already existing directive but I am halted in my proces. When loading the page I'm facing the following error:
Multiple directives [directive#1, directive#2] asking for isolated scope on <easymodal title="Test-Title" text="Text-Text" oncancel="show = false" onok="alert();">
The base directive looks like this:
Rohan.directive('easymodal', function () {
return {
restrict: 'E',
// priority: 200,
transclude: true,
replace: true,
scope:{
showModal: "=show",
callback: "=closeFunction",
dismissable: '&'
},
template:
'<div data-ng-show="showModal" class="modal-container">' +
'<div class="modal-body">' +
'<div class="title"><span data-translate></span><a data-ng-show="dismissable" data-ng-click="dismiss()">x</a></div>' +
'<div data-ng-transclude></div>' +
'</div>' +
'<div class="modal-backdrop" data-ng-click="dismiss()"></div>' +
'</div>'
};
});
And my wrapper directive looks like this:
Rohan.directive('easydialog', function () {
return {
restrict: 'E',
transclude: true,
scope: {title: '#',
text: '#',
onOk: '&',
onCancel: '&'},
replace: true,
template:
'<easymodal>' +
'{{text}} ' +
'<hr>' +
'<button ng-click="{{onCancel}}" value="Cancel"' +
'<button ng-click="{{onOk}}" value="Ok"' +
'</easymodal>'
};
});
My html looks like this:
<easydialog title="Test-Title" text="Text-Text" onCancel="show = false" onOk="alert();" />
At first I though my title attribute was conflicting so I removed that attribute in the html line and from my wrapper directive but it was not effective.
You need to change your easydialog template and wrap <easymodal> in a <div>.
Your problem simply is that you're adding a template argument inside the directive as well as adding a resolving template tag named <easydialog> in your actual HTML template. You have the choose either the one or the other.