I have written the following Angular directive:
angular.module('solarquote.directives', []).directive('editfield', function() {
return {
restrict: 'A',
transclude: true,
template: '<span ng-hide="editorEnabled" ng-transclude></span>' + // viewable field
'<span ng-show="editorEnabled"><input class="input-medium" ng-model="editableField"></span>', // editable field
link: function(scope, elm, attrs, ctrl) {
scope.editorEnabled = false;
scope.editableField = elm.children[0].children[0].innerText;
}
};
})
And in the html, inside a ng-repeat:
<span editfield>{{ item.fields.name }}</span>
I would like to prepopulate the input field in the directive's template with the same content in the ng-transclude. Going through the DOM and grabbing the text yields: {{ item.fields.name }} instead of the rendered data: "Bob" (or whatever name).
What is the best way to access the transcluded data?
Thanks
It is not possible to assign to ng-model an expression that you specify in transclusion block. This is because a transclusion block can be an expression like {{ functionValue() }} or {{ field1+':'+field2 }}. Angular simply does not know how to reverse those expressions.
What you can do, is provide a reference to the model you want to update. See the following punkler http://plunker.co/edit/NeEzetsbPEwpXzCl7kI1?p=preview (needs jQuery)
directive('editfield', function() {
var template = ''+
'<span ng-show="editorEnabled"><input class="input-medium" ng-model="editfield"></span>'+
'<span ng-hide="editorEnabled" ng-transclude></span>';
return {
restrict: 'A',
template: template,
scope:{
editfield:'='
},
transclude:true,
link: function(scope, element, attrs) {
var input = element.find('input');
input.on('blur',function(){
scope.editorEnabled=false;
scope.$apply();
});
element.bind('click',function(){
scope.editorEnabled=!scope.editorEnabled;
scope.$apply();
input.focus();
})
}
};
})
Related
I have the following problem:
I have a custom directive, which shows one or more tables, by using ng-repeat inside the template string. Inside each table, several other custom directives are placed. I want these to know the index of the element used, but cant manage to get this done. My code looks like this now:
.directive('myStuffText', function ($rootScope){
return {
restrict: 'A',
require: '^form',
replace: true,
scope: true,
template:
......
'<table border="1" ng-repeat="elt in myModel.newStuffList">
......
'<tr>' +
'<td colspan="3"><div my-add-some-editor my-element-index="$index"/></td>' +
'</tr>'
'</table>',
link: function (scope, elt, attrs){
scope.cockpitPolicyModel.newPolicyList = [];
}
};
})
Independently from how I try, I always get the string $index or {{$index}} in the template function of the my-add-some-editor directive, not the value of it..
Edit - added the nested directive:
.directive('myAddSomeEditor', function($rootScope){
return {
restrict: 'A',
require: '^form',
scope: true,
template: function ($scope, $attr){
return
.....
'<span id="myAddSomeEditor" name="myAddSomeEditor" class="form-control" my-generic-editor '+
'my-input-mapping="myModel.someText"></span>'
.....
;
}
};
})
That probably happens because in your my-add-some-editor directive you have this definition in the isolate scope:
myElementIndex: '#'
That's why you're getting the literal string of what you're writing there in the HTML.
Change that to:
myElementIndex: '='
EDIT: Since you said you're not using isolated scope, try this in the parent directive: try doing my-element-index="{{$index}}". And this in the child directive's link function:
link: function (scope, elem, attr) {
attr.$observe('index', function(val) {
scope.index = val;
});
}
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
How do I get the input element from within the directive before the template overwrites the contents?
html
<div xxx>
<input a="1" />
</div>
js
app.directive('xxx', function(){
return {
restrict: 'A',
template: '<p></p>',
replace: true, //if false, just leaves the parent div, still no input
compile: function(element, attrs) {
console.log(element);
return function (scope, iElement, iAttrs) {
}
}
};
});
i am on angular 1.0.x, I cannot pass in optional scope parameters with the '=?' syntax and i want to be able to override a portion of the default template of the directive in a very flexible way. instead of adding a scope variable or attribute everytime that I just plan on passing through the directive, I want to be able to supply the whole element to be used.
edit
the input must retain the scope of the directive, and not the parent.
edit
I am trying to include a partial template inside a directive that will overwrite a piece of the actual template. The piece I am including therefore needs to have access to the directive's scope and not the parent's.
Update
It seems if I do not provide a template or a template URL and instead replace the contents manually using the $templateCache I can have access to the inner elements. I want to let angular handle the template and the replacement though and just want to be able to access the contents in the directive naturally before they get replaced.
Solution
Plunkr
html
<body ng-controller="MainCtrl">
<div editable="obj.email">
<input validate-email="error message" ng-model="obj.email" name="contactEmail" type="text" />
</div>
</body>
js
app.controller('MainCtrl', function($scope) {
$scope.obj = {
email: 'xxx'
};
});
app.directive('editable', function($log){
return {
restrict: 'A',
transclude: true,
template: '<div ng-show="localScopeVar">{{value}}<div ng-transclude></div></div>',
scope: {
value: '=editable'
},
link: function(scope) {
scope.localScopeVar = true;
}
};
});
app.directive('validateEmail', function($log){
return {
restrict: 'A',
require: 'ngModel',
scope: true,
link: function(scope, el, attrs, ctrl) {
console.log(attrs['validateEmail']);
}
};
});
I believe you're looking for the transclude function (link is to 1.0.8 docs). You can see what's going on with:
app.directive('xxx', function($log){
return {
restrict: 'A',
transclude: true,
compile: function(element, attrs, transclude) {
$log.info("every instance element:", element);
return function (scope, iElement, iAttrs) {
$log.info("this instance element:", element);
transclude(scope, function(clone){
$log.info("clone:", clone);
});
}
}
};
});
I'm trying to make a generic form validatation indicator. If the form is valid, it displays saying that the form is valid, else it displays an error saying that the form isn't valid.
I'm using bookingForm.$valid or SOMEFORM.$valid - but the ng-show isn't working. I think the connection between {{formValid}} and the actual form model isn't right.
Here's my directive:
sharedServices.directive('formValid', function() {
return {
restrict: 'A',
scope: {
formValid: '#'
},
template: '<div class="alert alert-warning" ng-show="!{{formValid}}.$valid"><b>ATH!</b> Útfylling ekki í lagi</div>' +
'<div class="alert alert-success" ng-show="{{formValid}}.$valid"><b>OK!</b> Útfylling í lagi</div>',
link: function(scope, element, attrs) {
scope.formValid = attrs.formValid;
}
};
});
Here's my usage:
<div form-valid="bookingForm"></div>
<form name="bookingForm">...</form>
<div form-valid="contactForm"></div>
<form name="contactForm">...</form>
I want to pass the form name (model) into my directive's template and display accordingly whether $valid is true/false on the forms model. Do I need to compile the template or how can I accomplish this?
Here's a GIF showing that it doesn't work, bookingForm.$valid is correct in the HTML, but always false in the directive's template.
Because ng-show creates a new scope, you need access the form in the $parent scope. It's a bit tricky in terms of the model assignment. You can use attrs.$observe to assign the object instead of the value of $valid.
Please try this code:
sharedServices.directive('formValid', function () {
return {
restrict: 'A',
scope: {
formValid: '#'
},
template: '<div class="alert alert-warning" ng-show="!formValid.$valid"><b>ATH!</b> Útfylling ekki í lagi</div>' +
'<div class="alert alert-success" ng-show="formValid.$valid"><b>OK!</b> Útfylling í lagi</div>',
link: function (scope, element, attrs) {
attrs.$observe('formValid', function () {
scope.formValid = scope.$parent[attrs.formValid];
});
}
};
});
DEMO
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>',
};
});