So, have created a few angular directives -- picture "user controls" around common data entry elements, like a label-textbox pair, etc
The problem we are having is that the zValidate directive does not seem to work from within the directive. Is there something we need to do to make nested directives work?
Edit
Here are the relevant code snippets.
So, first, we have a little directive that adds a label-input pair:
app.directive('afLabelInputPair', function ($compile) {
var directive = {
restrict: 'A',
transclude: true,
replace: true,
scope: { //#textValue =twoWayBinding &oneWayBinding
labelText: '#labelText',
afModel: '=',
afId: '#',
afPlaceholder: '#'
},
templateUrl: './app/templates/af-label-input-pair.html',
link: function (scope, element, attrs) {
scope.opts = attrs;
$compile(element.contents())(scope);
}
}
return directive;
});
Next, we have the template html (this is what's returned from templateUrl:
<div class="form-group">
<label class="control-label" for="{{afId}}">{{labelText}}</label>
<input id="{{afId}}" class="form-control" ng-model="afModel" placeholder="{{afPlaceholder}}" data-z-validate />
</div>
But, we don't get a display of breeze validation errors when we use this directive.
Related
I break my head try to resolve this problem. Suppose that I have this custom directive:
app.directive("selectInput",function($compile){
return {
restrict: "E",
templateUrl: 'js/angular-app/test.html',
scope: {
formName:'=',
inputName:"=",
nameInput: "#",
ngModel: "=",
},
transclude: true,
replace: true,
link: function(scope, element, attrs, ctrl) {
...
},
}});
Here my templateurl test.html
<div
class="form-group"
ng-class="{'has-error has-feedback': formName.inputName.$invalid}">
<input type="text" name="{{nameInput}}" ng-model="ngModel"/></div>
And the call
<form name="form" class="simple-form" novalidate>
<select-input
form-name="form"
input-name="fClase"
name-input="fClase"
ng-model="inputmodel">
</select-input></form>
The problem is in test.html template, the expression formName.inputName.$invalid not work, I try {{formName}}.{{inputName}}.$invalid and nothing, also I try change the params in directive definition for &, # ... =?.
I have not been able to merge this expressions, I appreciate any help.
Update, fix the problem (Thanks to Joe Enzminger):
At the end I change the directive by this:
app.directive("selectInput",function($compile){
return {
restrict: "E",
templateUrl: 'js/angular-app/test.html',
scope: {
inputName: "#",
ngModel: "=",
},
require: ["^form"],
replace: true,
link: function(scope, element, attrs, ctrl) {
scope.form = ctrl[0];
...
},
}});
Note the form attr as ctrl.
The template test.html
<div
class="form-group"
ng-class="{'has-error has-feedback': form[inputName].$invalid}">
<input type="text" name="{{nameInput}}" ng-model="ngModel"/></div>
Here change formName.inputName.$invalid by form[inputName].$invalid
And finally the call
<form name="form" class="simple-form" novalidate>
<select-input
input-name="fClase"
ng-model="inputmodel">
</select-input></form>
I hopefully useful
Here is an alternative implementation that might clarify how things actually work.
The changes:
Don't inject $compile (you don't use it)
No transclude: true (you aren't using transclusion)
scope: {} - we create an empty isolate scope so that our template will work and we can isolate the directive scope from the parent scope.
require: ["ngModel", "^form"] - we want to require ngModel on the element and require that the element is embedded in a form. These will be delivered to the link functions ctrl parameter.
<div
class="form-group"
ng-class="{'has-error has-feedback': form[model.$name].$invalid}">
<input type="text" ng-model="model.$modelValue"/></div>
<form name="form" class="simple-form" novalidate>
<select-input
ng-model="inputmodel">
</select-input></form>
app.directive("selectInput",function(){
return {
restrict: "E",
templateUrl: 'js/angular-app/test.html',
scope: {},
replace: true,
require: ["ngModel", "^form"],
link: function(scope, element, attrs, ctrl) {
//give our directive scope access to the model and form controllers
$scope.model = ctrl[0];
$scope.form = ctrl[1];
},
}});
My overall aim is to componentize all our re-usable widgets as angular directives but am struggling when trying to make an angular form error message directive. I have read numerous posts but couldn't see how to achieve this.
All my directives have isolate scope.
My main issue is that I do not know how to get the pattern attribute to bind correctly to the ng-show attribute of the at-error-message directives span element so that the error message dynamically hides\shows based on the pattern.
The outer HTML is:
<body ng-controller="atVrmLookup">
<ng-form name="vrmForm" novalidate>
<at-input name="registration" label="Registration" required model="vrmLookup.registration" minlength="3"></at-input>
<at-error-message pattern="vrmForm.registration.$error.required" message="Please enter a registration"></at-error-message>
</ng-form>
</body>
The atInput directive is
uiComponents.directive('atInput', function () {
return {
// use an inline template for increased
template: '<div><label>{{label}}</label><div><input name="{{name}}" type="text" ng-model="model" ng-minlength="{{minlength}}"/></div></div>',
// restrict directive matching to elements
restrict: 'E',
scope: {
name: '#',
label: '#',
minlength: '#',
model:'=model'
},
compile: function (element, attr, scope) {
var input = element.find('input');
if (!_.isUndefined(attr.required)) {
input.attr("required", "true");
}
},
controller: function ($scope, $element, $attrs) {
// declare some default values
}
};
});
the atErrorMessageDirective is
uiComponents.directive('atErrorMessage', function () {
return {
// use an inline template for increased
template: '<span class="error" ng-show="pattern">{{message}}</span>',
// restrict directive matching to elements
restrict: 'E',
scope: {
message: '#',
pattern: '='
},
controller: function ($scope, $element, $attrs) {
// declare some default values
}
};
});
Here is plunkr to demonstrate the issue.
http://plnkr.co/edit/5tdsqSXg0y5bfQqJARFB
Any help would be appreciated.
The input name cannot be an angular binding expression. Instead, use a template function, and build your template string:
template: function($element, $attr) {
return '<div><label>{{label}}</label><div>' +
'<input name="' + $attr.name + '" type="text" ng-model="model" ng-minlength="{{minlength}}"/>' +
'</div></div>';
}
Alternate Solution
Implement a dynamic-name directive, and use the API exposed by the angular form directive to programmatically set the name of the ngModel, and add the ngModel control to the form:
.directive("dynamicName",[function(){
return {
restrict:"A",
require: ['ngModel', '^form'],
link:function(scope,element,attrs,ctrls){
ctrls[0].$name = scope.$eval(attrs.dynamicName) || attrs.dynamicName;
ctrls[1].$addControl(ctrls[0]);
}
};
}])
Then in your template:
template: '<div><label>{{label}}</label><div><input dynamic-name="{{name}}" type="text" ng-model="model" ng-minlength="{{minlength}}"/></div></div>',
Note: This solution works because fortunately, ngForm provides programmatic access to customize its behavior. If ngForm's controller had not exposed an API, then you might have been SOL. It's good practice to think of the API you expose from your own custom directive's controllers - you never know how it can be used by other directives.
In the following AngularJS code, when you type stuff into the input field, I was expecting the div below the input to update with what is typed in, but it doesn't. Any reason why?:
html
<div ng-app="myApp">
<input type="text" ng-model="city" placeholder="Enter a city" />
<div ng-sparkline ng-model="city" ></div>
</div>
javascript
var app = angular.module('myApp', []);
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
http://jsfiddle.net/AndroidDev/vT6tQ/12/
Add ngModel to the scope as mentioned below -
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
scope: {
ngModel: '='
},
template: '<div class="sparkline"><h4>Weather for {{ngModel}}</h4></div>'
}
});
Updated Fiddle
It should be
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
since you are binding the model to city
JSFiddle
The basic issue with this code is you aren't sharing "ngModel" with the directive (which creates a new scope). That said, this could be easier to read by using the attributes and link function. Making these changes I ended up with:
HTML
<div ng-sparkline="city" ></div>
Javascript
app.directive('ngSparkline', function ($compile) {
return {
restrict: 'A',
link: function (scope, element, attrs) {
var newElement = '<div class="sparkline"><h4>Weather for {{' + attrs.ngSparkline + '}}</h4></div>';
element.append(angular.element($compile(newElement)(scope)));
}
}
});
Using this pattern you can include any dynamic html or angular code you want in your directive and it will be compiled with the $compile service. That means you don't need to use the scope property - variables are inherited "automatically"!
Hope that helps!
See the fiddle: http://jsfiddle.net/8RVYD/1/
template: '<div class="sparkline"><h4>Weather for {{city}}</h4></div>'
the issue is that require option means that ngSparkline directive expects ngModel directive controller as its link function 4th parameter. your directive can be modified like this:
app.directive('ngSparkline', function () {
return {
restrict: 'A',
require: '^ngModel',
template: '<div class="sparkline"><h4>Weather for {{someModel}}</h4></div>',
link: function(scope, element, attrs, controller) {
controller.$render = function() {
scope.someModel = controller.$viewValue;
}
}
}
});
but this creates someModel variable in scope. that I think isn't necessary for this use case.
fiddle
I have a directive that can be used multiple times on a page. In the template of this directive, I need to use IDs for an input-Element so I can "bind" a Label to it like so:
<input type="checkbox" id="item1" /><label for="item1">open</label>
Now the problem is, as soon as my directive is included multiple times, the ID "item1" is not unique anymore and the label doesn't work correctly (it should check/uncheck the checkbox when clicked).
How is this problem fixed? Is there a way to assign a "namespace" or "prefix" for the template (like asp.net does with the ctl00...-Prefix)? Or do I have to include an angular-Expression in each id-Attribute which consists of the directive-ID from the Scope + a static ID. Something like:
<input type="checkbox" id="{{directiveID}} + 'item1'" /><label for="{{directiveID}} + 'item1'">open</label>
Edit:
My Directive
module.directive('myDirective', function () {
return {
restrict: 'E',
scope: true,
templateUrl: 'partials/_myDirective.html',
controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
...
} //controller
};
}]);
My HTML
<div class="myDirective">
<input type="checkbox" id="item1" /><label for="item1">open</label>
</div>
HTML
<div class="myDirective">
<input type="checkbox" id="myItem_{{$id}}" />
<label for="myItem_{{$id}}">open myItem_{{$id}}</label>
</div>
UPDATE
Angular 1.3 introduced a native lazy one-time binding. from the angular expression documentation:
One-time binding
An expression that starts with :: is considered a
one-time expression. One-time expressions will stop recalculating once
they are stable, which happens after the first digest if the
expression result is a non-undefined value (see value stabilization
algorithm below).
Native Solution:
.directive('myDirective', function() {
var uniqueId = 1;
return {
restrict: 'E',
scope: true,
template: '<input type="checkbox" id="{{::uniqueId}}"/>' +
'<label for="{{::uniqueId}}">open</label>',
link: function(scope, elem, attrs) {
scope.uniqueId = 'item' + uniqueId++;
}
}
})
Only bind once:
If you only need to bind a value once you should not use bindings ({{}} / ng-bind)
bindings are expensive because they use $watch. In your example, upon every $digest, angular dirty checks your IDs for changes but you only set them once.
Check this module: https://github.com/Pasvaz/bindonce
Solution:
.directive('myDirective', function() {
var uniqueId = 1;
return {
restrict: 'E',
scope: true,
template: '<input type="checkbox"/><label>open</label>',
link: function(scope, elem, attrs) {
var item = 'item' + uniqueId++;
elem.find('input').attr('id' , item);
elem.find('label').attr('for', item);
}
}
})
We add a BlockId parameter to the scope, because we use the id in our Selenium tests for example. There is still a chance of them not being unique, but we prefer to have complete control over them. Another advantage is that we can give the item a more descriptive id.
Directive JS
module.directive('myDirective', function () {
return {
restrict: 'E',
scope: {
blockId: '#'
},
templateUrl: 'partials/_myDirective.html',
controller: ['$scope', '$element', '$attrs', function ($scope, $element, $attrs) {
...
} //controller
};
}]);
Directive HTML
<div class="myDirective">
<input type="checkbox" id="{{::blockId}}_item1" /><label for="{{::blockId}}_item1">open</label>
</div>
Usage
<my-directive block-id="descriptiveName"></my-directive>
Apart from Ilan and BuriB's solutions (which are more generic, which is good) I found a solution to my specific problem because I needed IDs for the "for" Attribute of the label. Instead the following code can be used:
<label><input type="checkbox"/>open</label>
The following Stackoverflow-Post has helped:
https://stackoverflow.com/a/14729165/1288552
I'm trying to create a custom component that uses a dynamic ng-model inside-out the directive.
As an example, I could invoke different components like:
<custom-dir ng-model="domainModel1"></custom-dir>
<custom-dir ng-model="domainModel2"></custom-dir>
With a directive like:
app.directive('customDir', function() {
return {
restrict: 'EA',
require: '^ngModel',
scope: {
ngModel: '=dirValue',
},
template: '<input ng-model="dirValue" />',
link: function(scope, element, attrs, ctrl) {
scope.dirValue = 'New';
}
};
});
The idea is that the textbox from the directive would change if the model changes, and in the other way around.
The thing is that I've tried different approaches with no success at all, you can check one of this here: http://plnkr.co/edit/7MzDJsP8ZJ59nASjz31g?p=preview In this example, I'm expecting to have the value 'New' in both of the inputs, since I'm changing the model from the directive and is a bi-directional bound (=). But somehow is not bound in the right way. :(
I will be really grateful if someone give some light on that. Thanks in advance!
Something like this?
http://jsfiddle.net/bateast/RJmhB/1/
HTML:
<body ng-app="test">
<my-dir ng-model="test"></my-dir>
<input type="text" ng-model="test"/>
</body>
JS:
angular.module('test', [])
.directive('myDir', function() {
return {
restrict: 'E',
scope: {
ngModel: '='
},
template: '<div><input type="text" ng-model="ngModel"></div>',
};
});