Directives doesn't translate attributes after element replace with jQuery - angularjs

I tried to do directive, which include template on the fly, but there is one problem - after compile all attributes, including ng-model, aren't translate to new element and ng-model doesn't working.
Where I'm wrong?
Element code:
<input type-test="kendo">
Directive:
App.directive('typeTest', ['$templateCache', '$compile', '$http', 'Formatter',
function ($templateCache, $compile, $http, Formatter) {
return {
restrict: 'A',
scope: {
ngModel: '='
},
replace: true,
link: function(scope, element, attrs) {
$http.get(Formatter.getTemplateUrl(attrs.typeTest), {cache: $templateCache}).success(function(tplContent) {
var el = $compile(tplContent)(scope);
element.replaceWith(el);
});
}
}
}
]);
Formatter.getTemplateUrl() returns a url to template depend on input argument (attrs.typeTest).
Template to type-test="kendo":
<input type="text" kendo-drop-down-list k-data-source="list" k-data-text-field="'Name'" k-data-value-field="'Id'">
List is defined like [{Id: 1, Name: 'First'}, {Id: 2, Name: 'Second'}].

You shouldn't replace the element inside a linking function of a directive. The linking function should just set up event listeners to make the directive work. Place your logic inside the compile function instead of the link function. Here's a pretty good article about it: http://amitgharat.wordpress.com/2013/06/08/the-hitchhikers-guide-to-the-directive/

I find a solution:
App.directive('dynamicType', ['$compile',
function ($compile) {
return {
compile: function compile(tElement, tAttrs, transclude) {
return {
pre: function preLink(scope, iElement, iAttrs, controller) {
var tpl = "<input type-test='"+iAttrs.dynamicType+"'>";
iElement.html(tpl);
$compile(iElement.contents())(scope);
},
post: function postLink(scope, iElement, iAttrs, controller) {}
}
}
}
}
]);
This directive compile new element, then link it and after return control to typeTest directive - to compile and link other element.
Element:
<input dynamic-type="kendo">

Related

Directive gives me error when try to access scope variable from compile function

When I try to make directive for display content and tooltip but I need to initialize scope variable 'name' in compile function.
sampleApp.directive('secondPage',function($compile){
return {
restrict:'E',
template:'<h3>Second Page Directive Content Displaying using directive scope <u>{{name}}</u></h3>',
scope:false,
controller: function( $scope, $element, $attrs, $transclude ) {
$scope.name = 'Angular Js';
},
compile: function (element, attrs) {
element.attr('tooltip', '{{dt()}}');
element.attr('tooltip-placement', 'bottom');
element.removeAttr("common-things"); //remove the attribute to avoid indefinite loop
element.removeAttr("data-common-things"); //also remove the same attribute with data- prefix in case users specify data-common-things in the html
return {
pre: function preLink(scope, iElement, iAttrs, controller) { },
post: function postLink(scope, iElement, iAttrs, controller) {
$compile(iElement)(scope);
}
};
}
}
});
Html Code --
<div ng-controller="MySecondController">
<h2 ng-bind='message'></h2>
<second-page name='Second Page'></second-page>
</div>
function post(scope, iElem, iAttrs){
var name = iAttrs.name;
$compile(iElem)(angular.extend(scope.$new(), {name: name}));
}

I can pass a scope variable into a directive's link function, but not the compile function, angular

I'm using ng-repeat and I need to pass a scope variable into a directive's compile function. I know how to do it with the link function, but not the compile function.
My html looks like:
<div ng-repeat="item in chapter.main">
<block type="item.type"></block>
</div>
Let's say item.type="blah" no matter the item.
Then this link function works fine
app.directive('block', function() {
return {
restrict: 'E',
link: function(scope, element, attributes){
scope.$watch(attributes.type, function(value){
console.log(value); //will output "blah" which is correct
});
}
}
});
But I can't do the same with compile?
app.directive('block', function() {
return {
restrict: 'E',
compile: function(element, attrs, scope) {
scope.$watch(attrs.type, function(value){
console.log(value);
});
}
}
});
The error I get is "cannot read property $watch of undefined"..
This is how I'd like my directive to look like:
app.directive('block', function() {
return {
restrict: 'E',
compile: function(element, attrs) {
element.append('<div ng-include="\'{{type}}-template.html\'"></div>');
//or element.append('<div ng-include="\'{' + attrs.type + '}-template.html\'"></div>');
//except the above won't interpret attr.type as a variable, just as the literal string 'item.type'
}
}
});
The compile function doesn't have scope as one it's parameter.
function compile(tElement, tAttrs, transclude) { ... }
NOTE: transclude is deprecated in the latest version of Angular.
Is there any reason you don't want to use link?
From the DOC
The compile function deals with transforming the template DOM. Since most directives do not do template transformation, it is not used often. The compile function takes the following arguments:
tElement - template element - The element where the directive has been declared. It is safe to do template transformation on the element and child elements only.
tAttrs - template attributes - Normalized list of attributes declared on this element shared between all directive compile functions.
transclude - [DEPRECATED!] A transclude linking function: function(scope, cloneLinkingFn)
UPDATE
To access the scope from inside compile function, you need to have either a preLink or postLink function. In your case, you need only the postLink function. So this ...
compile: function compile(tElement, tAttrs, transclude) {
return function postLink(scope, element, attrs) { ... }
},
PROPOSED SOLUTION Might not be exact but should help you on your way.
html
<div ng-app="myApp" ng-controller="app">
<block type="item.type"></block>
</div>
JS (Controller + Directive)
var myApp = angular.module('myApp', []);
myApp.controller('app', function ($scope, $http) {
$scope.item = {
type: 'someTmpl'
};
}).directive('block', ['$compile', function ($compile) {
return {
restrict: 'AE',
transclude: true,
scope: {
type: '='
},
compile: function (element, attrs) {
return function (scope, element, attrs) {
var tmpl;
tmpl = scope.type + '-template.html';
console.log(tmpl);
element.append('<div ng-include=' + tmpl + '></div>');
$compile(element.contents())(scope);
};
}
};
}]);

How to force recompile of directive with isolated scope?

There are a few SO posts about refreshing a directive when a (model) value changes:
Angular Directive refresh on parameter change
AngularJS directive gets not updated if scope variable changes
The recommended approach is to watch a value on the scope in the linking function:
link: function(scope, element, attrs) {
scope.$watch("typeId",function(newValue,oldValue) {
// This gets called when data changes.
});
}
Given the following implementation of the recursive directive, <my-directive>:
angular.module('Myapp').directive("MyDirective", function(RecursionHelper) {
return {
restrict: "E",
scope: {
data: '=data'
},
templateUrl: 'view.html',
compile: function(tElement, tAttributes) {
return RecursionHelper.compile(tElement, tAttributes);
}
};
});
...and the RecursionHelper:
angular.module('Myapp').factory('RecursionHelper',
['$compile',
function($compile) {
var RecursionHelper = {
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
var compiledContents;
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone) {
iElement.append(clone);
});
},
pre: function(scope, iElement, iAttrs) { }
}
}
}
return RecursionHelper;
}]);
I could add the listener to my linking function:
angular.module('Myapp').factory('RecursionHelper',
['$compile','MyService',
function($compile, MyService) {
var RecursionHelper = {
compile: function(tElement, tAttrs) {
var contents = tElement.contents().remove();
var compiledContents;
return {
post: function(scope, iElement, iAttrs) {
if (!compiledContents) {
compiledContents = $compile(contents);
}
compiledContents(scope, function(clone) {
iElement.append(clone);
});
// The listener function.
scope.$watch(MyService.data,function(newValue,oldValue) {
// This gets called when data changes.
});
},
pre: function(scope, iElement, iAttrs) { }
}
}
}
return RecursionHelper;
}]);
I have added a Service, MyService, which contains data that may change and that I'm listening for. The first call to render the directive is started with main.html:
<my-directive data="data"></my-directive>
The directive's compile function is called and ultimately the template, view.html is rendered containing the recursive directive:
<span ng-repeat="item in data.items">
<my-directive data="item"></my-directive>
</span>
Since the <my-directive> data attribute has isolated scope, each nested directive will have its own 'sandboxed' scope.
Questions
My $watch function never gets called even though MyService.data is modified. Is this because the directive has isolated scope?
Assuming my $watch function is called, what is the actual implementation to trigger a recompilation? The SO posts just have comments of console.log statements, but I can't find anything on rendering the entire (root) directive again.

How to bind content of tag into into directive's scope?

Say I have a directive like such:
<my-directive>This is my entry!</my-directive>
How can I bind the content of the element into my directive's scope?
myApp.directive('myDirective', function () {
return {
scope : {
entry : "" //what goes here to bind "This is my entry" to scope.entry?
},
restrict: "E",
template: "<textarea>{{entry}}</textarea>"
link: function (scope, elm, attr) {
}
};
});
I think there's much simpler solution to the ones already given. As far as I understand, you want to bind contents of an element to scope during initialization of directive.
Given this html:
<textarea bind-content ng-model="entry">This is my entry!</textarea>
Define bind-content as follows:
directive('bindContent', function() {
return {
require: 'ngModel',
link: function ($scope, $element, $attrs, ngModelCtrl) {
ngModelCtrl.$setViewValue($element.text());
}
}
})
Here's a demo.
I may have found a solution. It relies on the transclude function of directives. It works, but I need to better understand transclusion before being sure this is the right way.
myApp.directive('myDirective', function() {
return {
scope: {
},
restrict: 'E',
replace: false,
template: '<form>' +
'<textarea ng-model="entry"></textarea>' +
'<button ng-click="submit()">Submit</button>' +
'</form>',
transclude : true,
compile : function(element,attr,transclude){
return function (scope, iElement, iAttrs) {
transclude(scope, function(originalElement){
scope.entry = originalElement.text(); // this is where you have reference to the original element.
});
scope.submit = function(){
console.log('update entry');
}
}
}
};
});
You will want to add a template config to your directive.
myApp.directive('myDirective', function () {
return {
scope : {
entry : "=" //what goes here to bind "This is my entry" to scope.entry?
},
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "E",
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
myApp.controller('fooController', function($scope){
$scope.foo = "BLAH BLAH BLAH!";
});
And then use your directive like this:
<div ng-controller="fooController">
<!-- sets directive "entry" to "foo" from parent scope-->
<my-directive entry="foo"></my-directive>
</div>
And angular will turn that into:
<div>THIS IS MY ENTRY</div>
Assuming that you have angular setup correctly and are including this JS file onto your page.
EDIT
It sounds like you want to do something like the following:
<my-directive="foo"></my-directive>
This isn't possible with ELEMENT directives. It is, however, with attribute directives. Check the following.
myApp.directive('myDirective', function () {
return {
template: "<div>{{ entry }}</div>", //**YOU DIDN'T HAVE A TEMPLATE**
restrict: "A",
scope : {
entry : "=myDirective" //what goes here to bind "This is my entry" to scope.entry?
},
link: function (scope, elm, attr) {
//You don't need to do anything here yet
}
};
});
Then use it like this:
<div my-directive="foo"></div>
This will alias the value passed to my-directive onto a scope variable called entry. Unfortunately, there is no way to do this with an element-restricted directive. What is preventing it from happening isn't Angular, it is the html5 guidelines that make what you are wanting to do impossible. You will have to use an attribute directive instead of an element directive.

How to add attributes of element to angular directive

I'm new to angular. I want to write a directive which has all the attributes that I added to it when using in html. For example:
This is my directive
'use strict';
app.directive('province', function($compile) {
return {
restrict: 'E',
link: function (scope, element, attrs, controller) {
var markup = "<select></select>";
var elem = angular.element(element);
elem.replaceWith($compile(markup)(scope));
}
};
})
HTML:
<province class="form-control" data-target"elemntId"></province>
I want my <select> contain the class and other attributes that I added to directive in html.
output that I want: <select class="form-control" data-target="elementId"></select>
I used angular.element(element).attr(attr);, but it does not worked;
Any help is appreciated in advance.
Edit
I want all the attributes that exist in attrs of link function to be added to markup.
I would iterate over directive's attr array and apply it to your template:
app.directive('province', function($compile) {
return {
restrict: 'E',
replace:true,
template: "<select></select>",
link: function (scope, element, attrs) {
var attr;
for (attr in attrs.$attr) {
if(attrs.hasOwnProperty(attr)){
element.attr(attr, attrs[attr]);
}
}
}
};
})
Directive Tag:
<province foo="bar" foo1="bar1"></province>
Compiled into:
<select foo="bar" foo1="bar1"></select>
Plunkr
Depending on your needs, you don't need to compile yourself. You can use template and replace instead.
app.directive('province', function() {
return {
restrict: 'E',
template: '<select></select>',
replace: true,
link: function (scope, element, attrs) {
}
};
});
See plnkr
You can make use of the attrs parameter of the linking function - this will get you the values of the attributes:
attrs.class and attrs.dataTarget are the ones you need.
You can take a look at the documentation here that elaborates further uses of the linking function

Resources