AngularJS add ng-model at compile time - angularjs

I have previously created a directive called click-to-edit
you would use it like
<click-to-edit="someValue">
where $scope.someValue=7;
I have since realized that i need to use the ngModel controller, and it would be EASIER if i could recompile this directive and add ng-model="someValue" to the template. Im having trouble doing this as it is giving me the error
"Error: [$compile:ctreq] Controller 'ngModel', required by directive 'clickToEdit', can't be found!"
This is obviously because i have
require:'ngModel'
this is a snippet of the code so far
return {
restrict: 'A',
replace: true,
require: 'ngModel',
priority: 100,
compile: function(tElement, tAttrs, transclude) {
// Correct ngModel for isolate scope
tAttrs.$set('ngModel', tAttrs.clickToEdit, false);
}
return {
post: linkFn
};
},
scope: {
dp: '=?',
type: '#',
fn: '=?', //<--- formula
editFn:'&?' // if you want to execute a function on a valid save, add this
}
What i simply want to do, is take a directive that looks like
<click-to-edit="model"/>
and change it to
<click-to-edit="model" ng-model="model"/>
and then have it compile and work as expected.
Let me know if you need other code.

I'm not sure if you've considered this - but why not just do:
<click-to-edit ng-model="model"/>
To answer the original question, I suggest adding a body directive:
.directive('body', function() {
return {
restrict: 'E',
compile: function(element, attr) {
$('[click-to-edit]', element).attr('ng-model', 'model');
}
}
})
Plunker Here

Related

binding data in a directive returns inconsistent values

i have this wierd problem with binding data to a directive. this is how i declare my directive:
<my-directive data="myArray"></my-directive>
my directive code looks like:
angular.module('ngApp')
.directive('myDirective', function () {
return {
scope:{
data: '='
},
template: '<div steps="data.length"></div>',
restrict: 'E',
link: function postLink(scope, element, attrs) {
console.log(scope);
console.log(scope.data);
}
};
});
in the first log, the data property is correct:
screenshot of console.log output
but the second log is undefined.
any idea why?
here is your solution :
<my-directive data-example="myArray"></my-directive>
you should always name your variables data-<name>. This is far easier to maintain and this prevent errors with keywords like this one ;)
angular.module('ngApp')
.directive('myDirective', function () {
return {
scope:{
data: '=example'
},
template: '<div steps="data.length"></div>',
restrict: 'E',
link: function postLink(scope, element, attrs) {
console.log(scope);
console.log(scope.data);
}
};
});
i hope this help,
Live example : JsFiddle
thanks to #hadiJZ and #Unex i figured it out:
my directive was nested in another directive. but the parent directive used the link function for the logic, since on creation it wasn't having child directives.
so moving the logic out to a controller solved my problem.
i figured it out when i added a $timeout to the child directive, and the log (scope.data) was correct.

'attrs' property in angular directive is not working properly

I am trying to write a very simple directive which involves setting a property based off one of the attributes that it is provided. The issue I'm facing is that the value of the attrs object is not being consistently recognized within the link function.
Here is the grand total of what I'm currently trying to achieve :
angular.module('directives').directive('wikiNotes',function() {
return {
restrict: 'EA',
templateUrl: 'common/directives/wiki-notes.tpl.html',
link: function(scope, element, attrs) {
console.log(attrs.openEdit); //true
if(attrs.openEdit===true){
console.log('open edit'); //not called
}
}
};
});
The console.log(attrs.openEdit) is showing as true, but then the console.log in the if block is not getting called. Am I missing something very obvious or is this a quirk with angular directives?
Did you consider adding this attribute in your directive scope section?
I think it is more in the Angular philosophy, but this imply you create a new scope for your directive.
angular.module('directives')
.directive('wikiNotes',function() {
return {
restrict: 'EA',
scope: {
openEdit: '='
},
templateUrl: 'common/directives/wiki-notes.tpl.html',
link: function(scope) {
console.log(scope.openEdit); //true
if(scope.openEdit===true){
console.log('open edit'); //should be a boolean
}
}
};
});
Here is a JS fiddle that demonstrate it works.
https://jsfiddle.net/c8mn9wka/3/

AngularJS How to access elements inside directive before they get replaced

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

Is the compile method appropriate for altering the template dynamically in a directive?

I have a directive where I want to add a select and either a textarea or an input field depending on some input. For example
app.directive('monkey', function() {
return {
restrict: 'E',
replace: true,
template: '<div><select><option>1</option><textarea>{{text}}</textarea></div>'
scope: {
text: "="
},
link: function(element, attrs, scope) {
// Add listeners etc
}
}
});
so the html would look something like
<monkey text="model.name" type="input"></monkey>
I want to be able to look at the type attribute and change my template from
<div><select><option>1</option><textarea>{{text}}</textarea></div>
to
<div><select><option>1</option><input>{{text}}</input></div>
Is the compile function what I should be using?
Yes, you could use the compile method for this.
But, if you have a limited set, I would opt for ng-if first. Just because it keeps things slightly more simple.
So the template becomes something like:
<div><select><option>1</option><textarea ng-if="type == 'textarea'">{{text}}</textarea><input ng-if="type == 'input'">{{text}}</input></div>
So, you would need to take type into your directive as well. Here is the entire directive:
app.directive('monkey', function() {
return {
restrict: 'E',
replace: true,
template: '<div><select><option>1</option><textarea ng-if="type == 'textarea'">{{text}}</textarea><input ng-if="type == 'input'">{{text}}</input></div>'
scope: {
text: "=",
type: "#"
},
link: function(element, attrs, scope) {
// Add listeners etc
}
}
});

Accessing attributes from an AngularJS directive

My AngularJS template contains some custom HTML syntax like:
<su-label tooltip="{{field.su_documentation}}">{{field.su_name}}</su-label>
I created a directive to process it:
.directive('suLabel', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
title: '#tooltip'
},
template: '<label><a href="#" rel="tooltip" title="{{title}}" data-placement="right" ng-transclude></a></label>',
link: function(scope, element, attrs) {
if (attrs.tooltip) {
element.addClass('tooltip-title');
}
},
}
})
Everything works fine, at the exception of the attrs.tooltip expression, which always returns undefined, even though the tooltip attribute is visible from Google Chrome's JavaScript console when doing a console.log(attrs).
Any suggestion?
UPDATE: A solution was offered by Artem. It consisted in doing this:
link: function(scope, element, attrs) {
attrs.$observe('tooltip', function(value) {
if (value) {
element.addClass('tooltip-title');
}
});
}
AngularJS + stackoverflow = bliss
See section Attributes from documentation on directives.
observing interpolated attributes: Use $observe to observe the value changes of attributes that contain interpolation (e.g. src="{{bar}}"). Not only is this very efficient but it's also the only way to easily get the actual value because during the linking phase the interpolation hasn't been evaluated yet and so the value is at this time set to undefined.
Although using '#' is more appropriate than using '=' for your particular scenario, sometimes I use '=' so that I don't have to remember to use attrs.$observe():
<su-label tooltip="field.su_documentation">{{field.su_name}}</su-label>
Directive:
myApp.directive('suLabel', function() {
return {
restrict: 'E',
replace: true,
transclude: true,
scope: {
title: '=tooltip'
},
template: '<label><a href="#" rel="tooltip" title="{{title}}" data-placement="right" ng-transclude></a></label>',
link: function(scope, element, attrs) {
if (scope.title) {
element.addClass('tooltip-title');
}
},
}
});
Fiddle.
With '=' we get two-way databinding, so care must be taken to ensure scope.title is not accidentally modified in the directive. The advantage is that during the linking phase, the local scope property (scope.title) is defined.

Resources