How to get inner html of custom directive? - angularjs

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

Related

How do I dynamically change class in Angular direcitves?

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>

Directive to show error messages

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.

Angular Directive Compile function: replace is ignored, and compiled content is nested

Directive:
app.directive('myCarousel', function ($compile) {
return {
restrict: 'E'
, transclude: false
, replace: true
, scope: true
, compile: function compile($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.html(html)
return function ($scope) {
$compile($element.contents())($scope);
}
}
};
})
Usage:
<my-carousel>
<li>Todd</li>
<li>Andrej</li>
</my-carousel>
Output
<my-carousel class="ng-scope">
<div id="carousel-1" class="rn-carousel-container ng-scope" style="width: 1600px;">
<div id="carousel-2" class="rn-carousel-container" style="width: 1600px;">
<blah>
</div>
</div>
</my-carousel>
The problems I have (at least known problems
1 - I still have the my-carousel element, why didn't the replace remove it? Do I need to do this myself because I am writing the compile function? HOw would I go about that?
2 - for some reason it looks like the rn-carousel inner directive is getting compiled inside of itself? This could very well be my lack of understanding on this inner directive on how it works. But does it look like anything is terribly wrong with this compile function?
When you use the compile function, you are expected to do DOM manipulation by yourself. You can achieve the same effect as replace: true using replaceWith:
compile: function($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.replaceWith(html);
return function(scope) {
/*probably don't need to compile element contents here*/
}
}
But... It doesn't look like you need to use compile at all. By giving the directive a template, and using transclusion, you can accomplish the same thing:
app.directive('myCarousel', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
scope: true,
template: '<ul rn-carousel="" rn-carousel-indicator="" ng-transclude></ul>'
};
})
Current working solution (albeit without replacing the parent element):
eido.directive('myCarousel', function ($compile) {
return {
restrict: 'E'
, transclude: false
, replace: true
, scope: true
, compile: function compile($element, $attr) {
var html = '<ul rn-carousel="" rn-carousel-indicator="">' + $element.html() + '</ul>'
$element.html(html)
return function ($scope) {
}
}
};
})

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

Ng-transclude data from template in link

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

Resources