How can I create a directive with a dynamic template?
'use strict';
app.directive('ngFormField', function($compile) {
return {
transclude: true,
scope: {
label: '#'
},
template: '<label for="user_email">{{label}}</label>',
// append
replace: true,
// attribute restriction
restrict: 'E',
// linking method
link: function($scope, element, attrs) {
switch (attrs['type']) {
case "text":
// append input field to "template"
case "select":
// append select dropdown to "template"
}
}
}
});
<ng-form-field label="First Name" type="text"></ng-form-field>
This is what I have right now, and it is displaying the label correctly. However, I'm not sure on how to append additional HTML to the template. Or combining 2 templates into 1.
i've used the $templateCache to accomplish something similar. i put several ng-templates in a single html file, which i reference using the directive's templateUrl. that ensures the html is available to the template cache. then i can simply select by id to get the ng-template i want.
template.html:
<script type="text/ng-template" id=“foo”>
foo
</script>
<script type="text/ng-template" id=“bar”>
bar
</script>
directive:
myapp.directive(‘foobardirective’, ['$compile', '$templateCache', function ($compile, $templateCache) {
var getTemplate = function(data) {
// use data to determine which template to use
var templateid = 'foo';
var template = $templateCache.get(templateid);
return template;
}
return {
templateUrl: 'views/partials/template.html',
scope: {data: '='},
restrict: 'E',
link: function(scope, element) {
var template = getTemplate(scope.data);
element.html(template);
$compile(element.contents())(scope);
}
};
}]);
Had a similar need. $compile does the job. (Not completely sure if this is "THE" way to do it, still working my way through angular)
http://jsbin.com/ebuhuv/7/edit - my exploration test.
One thing to note (per my example), one of my requirements was that the template would change based on a type attribute once you clicked save, and the templates were very different. So though, you get the data binding, if need a new template in there, you will have to recompile.
You should move your switch into the template by using the 'ng-switch' directive:
module.directive('testForm', function() {
return {
restrict: 'E',
controllerAs: 'form',
controller: function ($scope) {
console.log("Form controller initialization");
var self = this;
this.fields = {};
this.addField = function(field) {
console.log("New field: ", field);
self.fields[field.name] = field;
};
}
}
});
module.directive('formField', function () {
return {
require: "^testForm",
template:
'<div ng-switch="field.fieldType">' +
' <span>{{title}}:</span>' +
' <input' +
' ng-switch-when="text"' +
' name="{{field.name}}"' +
' type="text"' +
' ng-model="field.value"' +
' />' +
' <select' +
' ng-switch-when="select"' +
' name="{{field.name}}"' +
' ng-model="field.value"' +
' ng-options="option for option in options">' +
' <option value=""></option>' +
' </select>' +
'</div>',
restrict: 'E',
replace: true,
scope: {
fieldType: "#",
title: "#",
name: "#",
value: "#",
options: "=",
},
link: function($scope, $element, $attrs, form) {
$scope.field = $scope;
form.addField($scope);
}
};
});
It can be use like this:
<test-form>
<div>
User '{{!form.fields.email.value}}' will be a {{!form.fields.role.value}}
</div>
<form-field title="Email" name="email" field-type="text" value="me#example.com"></form-field>
<form-field title="Role" name="role" field-type="select" options="['Cook', 'Eater']"></form-field>
<form-field title="Sex" name="sex" field-type="select" options="['Awesome', 'So-so', 'awful']"></form-field>
</test-form>
One way is using a template function in your directive:
...
template: function(tElem, tAttrs){
return '<div ng-include="' + tAttrs.template + '" />';
}
...
If you want to use AngularJs Directive with dynamic template, you can use those answers,But here is more professional and legal syntax of it.You can use templateUrl not only with single value.You can use it as a function,which returns a value as url.That function has some arguments,which you can use.
http://www.w3docs.com/snippets/angularjs/dynamically-change-template-url-in-angularjs-directives.html
I managed to deal with this problem. Below is the link :
https://github.com/nakosung/ng-dynamic-template-example
with the specific file being:
https://github.com/nakosung/ng-dynamic-template-example/blob/master/src/main.coffee
dynamicTemplate directive hosts dynamic template which is passed within scope and hosted element acts like other native angular elements.
scope.template = '< div ng-controller="SomeUberCtrl">rocks< /div>'
I have been in the same situation, my complete solution has been posted here
Basically I load a template in the directive in this way
var tpl = '' +
<div ng-if="maxLength"
ng-include="\'length.tpl.html\'">
</div>' +
'<div ng-if="required"
ng-include="\'required.tpl.html\'">
</div>';
then according to the value of maxLength and required I can dynamically load one of the 2 templates, only one of them at a time is shown if necessary.
I heope it helps.
Related
I have created a directive with a template specified. I would like to see whatever i type in the debug div below the textbox. But debug shows only the initial value and its not getting updated. I think there is some silly mistake that i might be doing. But i am not able to figure it out.
Following is the directive:
angular.module('directiveBinding', [])
.directive('mydirective', function() {
var my_template = '<div contenteditable="true" ng-model="myobj.text"' +
'class="contenteditable col-xs-12 col-md-6 col-md-offset-3">{{myobj.text}}</div>' +
'<div class="debug clearfix"><br>{{myobj.text}}</div>';
var linker = function(scope, element, attrs, ngModel) {
console.log('Linker called');
console.log('scope.myobj.text: ' + scope.myobj.text);
}
var controller = function($scope) {
$scope.myobj = {};
$scope.myobj.text = 'some value';
$scope.text = "hello world"
console.log('$scope.myobj.text: ' + $scope.myobj.text);
}
return {
require: '?ngModel',
restrict: "E",
link: linker,
controller: controller,
template: my_template,
scope: {}
};
});
And i am using it as follows:
<mydirective></mydirective>
Edit
The above code works if i change the template to use <input> instead of contenteditable div.
var my_template = '<input type="text" ng-model="myobj.text"' +
'class="contenteditable col-xs-12 col-md-6 col-md-offset-3">{{myobj.text}}</input>' +
'<div class="debug clearfix"><br>{{myobj.text}}</div>';
Here is the plunker for the same.
I believe your issue lies with $scope.text, you should never bind to primitives.
In your html change ng-model="text.something" and also {{text}} to {{text.something}}.
My ngMessages doesnt work inside my directives template!
I have a directive myInput with a template and a link function, inside the template function I create template string for a wrapped <label> and <input>.
Inside the Link function I use the require: '^form' FormController and retrieve the form name. Then I'm putting a ngMessages block after the wrapped elements.
(function () {
'use strict';
angular
.module('app.components')
.directive('myInput', MyInput);
/*#ngInject*/
function MyInput($compile, ValidatorService, _, LIST_OF_VALIDATORS) {
return {
require: '^form',
restrict: 'E',
controller: MyInputController,
controllerAs: 'vm',
bindToController: true,
template: TemplateFunction,
scope: {
label: '#',
id: '#',
value: '=',
validateCustom: '&'
},
link: MyInputLink
};
function MyInputController($attrs) {
var vm = this;
vm.value = '';
vm.validateClass = '';
vm.successMessage = '';
vm.errorMessage = '';
}
function TemplateFunction(tElement, tAttrs) {
return '<div class="input-field">' +
' <label id="input_{{vm.id}}_label" for="input_{{vm.id}}" >{{vm.label}}</label>' +
' <input id="input_{{vm.id}}" name="{{vm.id}}" ng-class="vm.validateClass" type="text" ng-model="vm.value" >' +
'</div>';
}
function MyInputLink(scope, element, attrs, form){
var extra = ' <div ng-messages="' + form.$name + '.' + scope.vm.id + '.$error">' +
' <div ng-messages-include="/modules/components/validationMessages.html"></div>' +
' </div>';
$(element).after(extra);
}
}
})();
Usage:
<h1>Test</h1>
<form name="myForm">
<my-input label="My Input" id="input1" value="vm.input1"></my-input>
-------
<!-- this block is hardcoded and is working, it does not come from the directive! -->
<div ng-messages="myForm.input1.$error">
<div ng-messages-include="/modules/components/validationMessages.html"></div>
</div>
</form>
Instead of adding the ngMessages block inside the link function, add it inside the compile function.
It is not as handy as in the link funciton because of the missing FormController, but it works :-)
Here the code:
compile: function(tElement, tAttrs){
var name = tElement.closest('form').attr('name'),
fullFieldName = name + '.' + tAttrs.id; // or tAttrs.name
var extra = '<div ng-messages="' + fullFieldName + '.$error">' +
' <div ng-messages-include="/modules/components/validationMessages.html"></div>' +
'</div>';
$(element).after(extra);
Here is what I did, I added to scope, myForm: '=' then in the directive's template referred to <div ng-messages="vm.myForm[vm.id].$error" >
I feel this is much cleaner than mucking around in the link function.
How do I create an angular directive that adds a input in a form but also works with form validation.
For instance the following directive creates a text input:
var template = '\
<div class="control-group" ng-class="{ \'error\': {{formName}}[\'{{fieldName}}\'].$invalid}">\
<label for="{{formName}}-{{fieldName}} class="control-label">{{label}}</label>\
<div class="controls">\
<input id="{{formName}}-{{fieldName}}" name="{{fieldName}}" type="text" ng-model="model" />\
</div>\
</div>\
';
angular
.module('common')
.directive('formTextField', ['$compile', function ($compile) {
return {
replace: true,
restrict: 'E',
scope: {
model: '=iwModel',
formName: '#iwFormName',
fieldName: '#iwFieldName',
label: '#iwLabel'
},
transclude: false,
template: template
};
}])
;
However the input is not added to the form variable ($scope.formName). I've been able to work around that by using the dynamicName directive from the following stackoverflow question Angularjs: form validation and input directive but ng-class will still not work.
Update
It now seems to be working but it feels like a hack. I thought I needed the scope to grab the form and field names however I can read that directly from the attributes. Doing this shows the field in the controllers scope form variable. However the ng-class attribute does not apply. The only way around this is to add the html element a second time once the scope is available.
jsFiddle here
var template = '\
<div class="control-group">\
<label class="control-label"></label>\
<div class="controls">\
<input class="input-xlarge" type="text" />\
</div>\
</div>\
';
angular
.module('common')
.directive('formTextField', ['$compile', function ($compile) {
return {
replace: true,
restrict: 'E',
scope: false,
compile: function compile(tElement, tAttrs, transclude) {
var elem = $(template);
var formName = tAttrs.iwFormName;
var fieldName = tAttrs.iwFieldName;
var label = tAttrs.iwLabel;
var model = tAttrs.iwModel;
elem.attr('ng-class', '{ \'error\': ' + formName + '[\'' + fieldName + '\'].$invalid }');
elem.find('label').attr('for', formName + '-' + fieldName);
elem.find('label').html(label);
elem.find('input').attr('id', formName + '-' + fieldName);
elem.find('input').attr('name', fieldName);
elem.find('input').attr('ng-model', model);
// This one is required so that angular adds the input to the controllers form scope variable
tElement.replaceWith(elem);
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
// This one is required for ng-class to apply correctly
elem.replaceWith($compile(elem)(scope));
}
};
}
};
}])
;
when I do something like this, I use the directive compile function to build my html prior to it being processed. For example:
myApp.directive('specialInput', ['$compile', function($compile){
return {
// create child scope for control
scope: true,
compile: function(elem, attrs) {
// use this area to build your desired dom object tree using angular.element (eg:)
var input = angular.element('<input/>');
// Once you have built and setup your html, replace the provided directive element
elem.replaceWith(input);
// Also note, that if you need the regular link function,
// you can return one from here like so (although optional)
return function(scope, elem, attrs) {
// do link function stuff here
};
}
};
}]);
I try to write a very flexible directive. For doing so i want the user to define a template used in part of my return (as seen in the ui-bootstrap typeahead directive).
So i define my template like this:
<script type="text/ng-template" id="myDirectivesCustomTemplate.html">
<ul>
<li ng-repeat="value in values">
<a ng-click="doSomething(value.id)">
{{value.name}}
</a>
</li>
</ul>
</script>
I set this template in my directive
<div
my-directive
my-directive-custom-template="myDirectivesCustomTemplate.html"
my-directive-data="someScopeData">
Now in my directive, how can i render the custom template and use it with the passed data? When i try to use it to return in template directly it throws a ReferenceError: $scope is not defined Error. If i call it without scope, it says ReferenceError: myDirectiveCustomTemplate is not defined Error.
Where and how can i use my template if i do not just want to use it as a return directly?
EDIT: let's say, this is my directive:
(function() {
'use strict';
var Combobox = function() {
var displayInputField = elem.find('input.dropdown');
scope.$watch(scope.nsdComboboxModel,function(newVal){
/* search for newVal in given data object */
scope.setDisplayInputValue(newVal);
});
scope.setDisplayInputValue = function(value)
{
displayInputField.val(value);
};
scope.elementSelected = function (item, model, label) {
scope.ComboboxCallback(item);
scope.setDisplayInputValue(label);
};
}
return {
restrict: 'A',
transclude: true,
scope: {
Combobox: '#', /* used as HTML/CSS-id/name/path */
ComboboxModel: '=', /* the actual AngularJS model (ng-model) */
ComboboxAutocompleteData: '=', /* the data used for autoComplete (must be array of objects having id and value) */
ComboboxDropdownData: '=', /* data used by the dropdown template */
ComboboxCallback: '=', /* a callback function called with selected autocomplete data item on select */
ComboboxLabel: '#', /* label for the input field */
ComboboxDropdownTemplate: '#' /* label for the input field */
},
template:
'<section class="-combobox-directive container-fluid">' +
'<label for="{{Combobox}}" ng-if="ComboboxTranslation" translate="{{ComboboxLabel}}"></label>' +
'<div class="combobox input-group">' +
'<input type="text" ' +
'id="{{Combobox}}" ' +
'autocomplete="off" ' +
'ng-model="ComboboxDestinationDisplay" ' +
'data-toggle="dropdown" ' +
'typeahead="value as location.value for location in ComboboxAutocompleteData | filter:$viewValue" ' +
'typeahead-editable="false" ' +
'typeahead-on-select="elementSelected($item, $model, $label)" ' +
'class="form-control dropdown">' + // dropdown-toggle
'<span data-toggle="dropdown" class="input-group-addon dropdown-toggle">' +
'<span class="glyphicon glyphicon-globe"></span>' +
'</span>' +
//$compile(ComboboxDropdownTemplate) +
'</div>' +
'</section>',
link: link
};
};
angular.module('vibe.directives').directive('nsdCombobox', [NsdCombobox]);
})();
HTML
<script type="text/ng-template" id="myDirectivesCustomTemplate.html">
<ul>
<li ng-repeat="value in values">
<a ng-click="doSomething({id:value.id})">
{{value.name}}
</a>
</li>
</ul>
</script>
<div ng-controller="MainCtrl">
<div my-directive template="myDirectivesCustomTemplate.html" mydata="mydata" mycallback="doSomething(id)"></div>
</div>
JS
app.controller('MainCtrl',function($scope){
$scope.mydata = [{id:1,name:'One'},{id:2,name:'Two'},{id:3,name:'Three'}];
$scope.doSomething = function(id){
alert(id);
}
});
app.directive('myDirective', function($templateCache,$compile) {
return {
restrict: 'A',
scope:{
template : "#",
mydata : "=",
mycallback:"&"
},
link: function(scope,element) {
var template = $templateCache.get(scope.template);
scope.values = scope.mydata;
scope.doSomething = scope.mycallback;
element.append($compile(template)(scope));
}
}
});
Looking at your directive i can suggest try ng-include. Where you want to do
//$compile(ComboboxDropdownTemplate) +
'</div>'
change it to
<span ng-include='templateUrlPropertyOnScope'>
'</div>'
templateUrlPropertyOnScope property should point to a template either on server side or in script section created with type=text/ng-template.
I know this is 4 years later but if anyone still has this question, perhaps this answer may also be found useful.
With a simple directive such as this:
<my-directive template="custom-template.html"></my-directive>
You can then refer to the template attribute in your directive, like this:
(function() {
angular
.module('app')
.directive('myDirective', myDirective);
function myDirective() {
return {
restrict: 'E',
templateUrl: function(elem, attrs) {
return attrs.template;
}
}
}
}
you can use $http and $compile to achieve such a task.
app.directive('myDirective', function($http, $templateCache, $compile) {
return {
scope: {
// reference to your data.
// Just use `data.values` or `data.whatever` in your template
data: '=myDirectiveData'
},
link: function(scope, elm, attrs) {
// Load the template via my-directive-custom-template attribute
$http.get(attrs.myDirectiveCustomTemplate, {cache: $templateCache}).success(function(html) {
// update the HTML
elm.html(html);
// compile the html against the scope
return $compile(elm.contents())(scope);
});
}
};
});
I hope it gives you a good start
I'm trying to add an input element with ng-model inside a directive.
my code
the link function of my directive:
link: function (scope, element, attrs) {
var elem_0 = angular.element(element.children()[0]);
for (var i in scope.animals[0]) {
elem_0.append(angular.element('<span>' + scope.animals[0][i].id + '</span>'));
//this part doesn't work
var a_input = angular.element('<input type="text">');
a_input.attr('ng-model', 'animals[0][' + i + '].name');
//end
elem_0.append(a_input);
}
it seems i need to call $compile() at the end, but have no idea how.
Try
var a_input = angular.element($compile('<input type="text" ng-model="animals[0][' + i + '].name"/>')($scope))
elem_0.append(a_input);
You are making directive more complicated than necessary by manually looping over arrays when you could use nested ng-repeat in the directive template and let angular do the array loops:
angular.module("myApp", [])
.directive("myDirective", function () {
return {
restrict: 'EA',
replace: true,
scope: {
animals: '=animals'
},
template: '<div ng-repeat="group in animals">'+
'<span ng-repeat="animal in group">{{animal.id}}'+
'<input type="text" ng-model="animal.name"/>'+
'</span><hr>'+
'</div>'
}
});
DEMO: http://jsfiddle.net/Ajsy7/2/