Two way binding not working with contenteditable in directive template - angularjs

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}}.

Related

How to create a angular input directive that works with form validation

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
};
}
};
}]);

Angularjs directive for checkbox with ng-model

I have a directive that work on check-box that also has ng-model.
In the directive on link function the check box not get the value of the model.
It is work if add timeout( doesn't matter how long, even with 0 ).
My control and directive:
var myApp = angular.module("myApp",[])
.directive("checkBox", function($timeout){
return {
restrict: 'A',
link: function (scope, element, attrs, ctrl) {
console.log("Check box is : " + element[0].checked);
scope.message += "Check box is : " + element[0].checked + " , ";
$timeout(function(){
scope.message += "Check box is : " + element[0].checked;
console.log("Check box is : " + element[0].checked);
},0);
}
}
});
function myCtrl($scope){
$scope.checkBoxModel = true;
$scope.message = "";
}
HTML:
<div ng-app="myApp" ng-controller="myCtrl" >
<input type="checkbox" ng-model="checkBoxModel" check-box>
<br/>
{{message}}
</div>
Fiddel - http://jsfiddle.net/myyjL/
Thanks in advance.
You probably should not do what you are trying to do, but it is possible.
Here is a plunkr that works, without timeout:
http://jsfiddle.net/myyjL/2/
.directive("checkBox", ['$timeout', function($timeout){
return {
restrict: 'A',
replace: false,
scope: {
ngModel:'='
},
transclude: true,
link: function (scope, element, attrs, ctrl) {
scope.$watch('ngModel', function(newValue){
scope.$parent.message = 'Checkbox value is: ' + newValue;
});
}
}
}])
The problem with that approach is that your directive knows about stuff that is outside of it (as in the {{message}} variable). That is a bad design and you should rework that. Also, in your fiddle you use the element[0].checked, while you can easily use the ng-modal value. But the ng-modal might not be there. Also, the input might not be a real checkbox, so your approach also fails. How to fix that? I'm gonna write you a demonstrative plunkr in a sec:
http://plnkr.co/edit/jyY6sQwXmtlGOvpdzETR?p=preview
angular.module('myApp', [])
.directive('box', [function(){
return {
restrict: 'E',
scope: {
ngModel: '='
},
templateUrl: 'box.tpl.html',
replace: true
};
}])
.controller('MyController', ['$scope', function MyController($scope){
$scope.checkboxValue = true;
}]);
What we did here is make sure our directive is always a checkbox by providing the checkbox inside the template. The you are able to do styling etc, of your directive.

AngularJS - Different template in directive

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>',
};
});

angular, in directive, adding to the template an element with ng model

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/

How to create a directive with a dynamic template in AngularJS?

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.

Resources