Angularjs - Pass optional parameter for Directive from HTML - angularjs

I am trying to pass an optional parameter isvalid from my html to the directive. I have followed all the steps mentioned in documentation but it still looks like i am doing something wrong.. i am not able to read the value in my directive. can you let me know what am i doing wrong?
HTML
<customvideo isvalid="true"></div>
MY Directive
Update: I had simplified for questioning purpose and hence you were seeing $scope. I have updated the actual directive now
// Set the directive
angular
.module('custom.directives')
.directive('customvideo', customvideoDirective);
// Set the directive $injections
customvideoDirective.$inject = ['$Scope'];
function customvideoDirective($Scope)
{
return {
compile: compile,
restrict: 'A',
$Scope: {
isvalid: '=?'
}
};
function compile() {
console.log($Scope.isvalid); //this is undefined
}
}
})();

Change you $Scope property inside Directive Definition Object (DDO) should be changed scope. Also use link function instead of using compile, as compile function has access to only raw DOM, there will be no scope available in it. Even you can return and PreLink/PostLink from compile function, but in this case I believe using link would be appropriate.
angular.module('directives')
.directive('customvideo', function () {
return {
link: link,
restrict: 'A',
scope: {
isvalid: '=?'
}
}
);
function link(scope) {
console.log(scope.isvalid);
}

Found a way to work it out.. not sure if it is the best.. but hey.. it works :) Posting it here for those who come here for answers...
I was writing the syntax for compile wrongly... I changed it to
function compile(tElement, tAttrs, transclude)
{
// the tAttrs param has all the attributes associated with the element
console.log(tAttrs.isvalid);
}

Related

Provide template with expressions as attribute on directive

I'm wanting to pass a "template" into a directive, by means of an attribute. Here's a trite example of what I'm trying to accomplish:
This HTML:
<greeter person-name="Jim" greeting-template="Hello {{name}}"></greeter>
Would produce output: Hello Jim.
I've tried with a directive like this:
function greeter($interpolate) {
var directive = {
link: link,
restrict: 'EA',
template: '<div>{{evaluatedTemplate}}</div>'
};
return directive;
function link(scope, element, attrs) {
scope.name = attrs.personName;
scope.evaluatedTemplate = $interpolate(attrs.greetingTemplate)(scope);
}
}
But that doesn't work, because {{name}} in the greeting-template attribute gets evaluated in the parent scope before it gets as far as the directive link function.
Ultimately, I would need the value of attrs.greetingTemplate to literally be a string of: 'Hello {{name}}'. I figure I could do it with some alternative syntax, like having the greeting-template attribute value as: "Hello [name]" and convert "[" to "{{" before interpolation. But that feels messy. I looked at transclusion too, but the way it evaluates the directive against the parent scope looks like it could cause issues when I have multiple greeter's.
Instead of using the link function, you could use the compile function, which runs before any linking to a scope occurs, and gets passed the template element (the original DOM element) as well as its uninterpolated attributes as arguments. I think that's what you're looking for here.
In the compile function, you could store the uninterpolated template string in a variable for later use in your post-link function (which is the same as the link function if you use link rather than compile), where you can then bind it to your scope.
So your directive would look like this, with a compile property rather than a link property:
function greeter($interpolate) {
var directive = {
compile: compile,
restrict: 'EA',
scope: true,
template: '<div>{{evaluatedTemplate}}</div>'
};
return directive;
function compile(tElement, tAttrs) {
// save the uninterpolated template for use in our post-link function
var greetingTemplateUninterpolated = tAttrs.greetingTemplate;
return {
pre: function (scope, element, attrs) {},
post: function (scope, element, attrs) {
scope.name = attrs.personName;
scope.evaluatedTemplate = $interpolate(greetingTemplateUninterpolated)(scope);
}
};
}
}
Here's a fiddle showing it working.
And here's a really good article explaining how compile and link work.

angular directive with isolate scope, fields inaccessible

I'm trying to write a directive which takes a scope variable name and assigns to it the result of passing a different named parameter into a function. Below, the files="result" is intended to create a {{result}} variable in the glob isolate scope. The contents of the "matching" variable are to be evaluated in the parent context, and assigned to an isolate 'matching' variable.
the directive then calls a function eventually assigning to the isolate variable pointed to by files (result here) the array returned. expansion of {{result}} could then be used for example in an ng-repeat.
The directive should be reusable without changing the variable names.
This isn't happening. If I assign everything to a parent, I can get it working but need to change the variable names each time.
angular.module('j20-glob', ['api'])
/*
* usage: <glob files="result" matching="/bin/{{prefix}}*">
* {{result}}
* </glob>
* should allow another just after the first without stomping result
* <glob files="result" matching="/something">{{result}}</glob>
*/
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
link: function(scope, iElement, iAttributes) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
});
}
}
}
]);
If I understand your code here, you are having a similar issue to what I answered here: Directive doesn't work when I which the version of Angular to 1.0.1 to 1.2.27.
You have created an Element Directive, called glob. The Directive has an Isolate Scope, which you attach a property, result in your example. This all works fine. The problem is, the property in the isolate scope is only accessible within the directive; and in your case, you are trying to access it outside the directive.
The Element <glob></glob> is your directive. This Element can be containers for other Elements, for example an angular expression {{result}} but these Elements are not part of the Directive, and therefore not scoped in the isolate.
If you were to include a template, and place {{result}} inside the template, you would see the expected result. However, this stops working if you change the variable you are passing in.
A rough draft of a working Directive using a transclude function might be something like:
.directive('glob', ['$parse', 'api', function($parse, $api) {
return {
priority: 99, // it needs to run after the attributes are interpolated
restrict: 'AE',
scope: {
},
transclude : true,
link: function(scope, iElement, iAttributes, ctrl, transclude) {
var indexModel = $parse(iAttributes.files);
iAttributes.$observe('matching', function(value) {
if (!value)
return;
$api.glob(value).then(function(res) {
indexModel.assign(scope, res);
// indexModel.assign(scope.$parent, res);
});
//append our scope into the DOM element (clone) instead of $scope
transclude(scope, function(clone, scope){
element.append(clone);
});
});
}
}
}
]);

How do I access the transclude function from a directive link function

Short question
How do I get access to the transclude function within a directive link function using Angular 1.1.1?
What I'm trying to achieve
Here is the (broken) fiddle http://jsfiddle.net/michaeldausmann/7NXZs/
I am trying to write a 'wrapper' directive...
<wrapper-dynamic>
<h2>Wrap me Dynamically!</h2>
</wrapper-dynamic>
...the dynamic template will contain an ng-transclude...
var tmpl = "<div>Dynamic Wrapper version {{wrapperVersion}}</div><hr/><div ng-transclude></div>";
...and I am $compiling it in the link function....
var thing = $compile(tmpl)(scope)
element.append(thing);
This fails with an error...."undefined is not a function"
I think I need to pass a transclude function to the $compile...
$compile(tmpl, transcludefn)(scope)
but I'm not sure how to access the transclude function... it does not appear to be available in the link function parameters like it is in later versions of angular.
Michael
Before AngularJS 1.2 you had to define the compile function to get a reference to the transclude function:
app.directive('myDirective', function($compile) {
return {
transclude: true,
compile: function compile(tElement, tAttrs, transcludeFn) {
return function postLink(scope, element, attributes) {
console.log(transcludeFn);
};
}
};
});
Demo: http://plnkr.co/edit/cNfwlf1RgMiDTASiLNIJ?p=preview

Manipulating DOM in directive, now what?

I have made a directive (inline-edit) and manipulated the DOM in the compile function, but how can I make the other directives that I have added to work? I guess I need to compile it, but how? See my jsfiddle here: http://jsfiddle.net/tidelipop/m4gbZ/
ng-click does not work as it is, but the strange thing is, why do ng-bind work? You can see that it does work if you unhide the textarea in dev tools.
angular.module('MyApp', [], function($compileProvider){
$compileProvider.directive("inlineEdit", function($compile, $q){
return {
restrict: "A",
scope: true,
controller: function($scope){
$scope.editMode = true;
$scope.save = function(){
console.log("Saving...");
};
},
compile: function(tElement, tAttrs){
tElement
.attr("ng-hide", "editMode")
.attr("ng-click", "editMode=!editMode")
.after("<textarea ng-show=\"editMode\" ng-model=\""+tAttrs.ngBind+"\"></textarea><button ng-click=\"save()\">Save</button>");
//var scopeResolver = $q.defer();
//$compile(tElement.parent().contents())(scopeResolver.promise);
return function(scope, element, attrs, controller){
//scopeResolver.resolve(scope);
//$compile(element.parent().contents())(scope);
console.log(element.parent().contents());
};
}
};
});
})
.controller("UserAdminCtrl", function($scope){
$scope.data_copy = {
user: {
user_id: 'sevaxahe',
comment: 'test'
}
};
});
It looks like your directive is conflicting with the ng-bind, I don't really know why, but the question I asked myself looking at your code was : Wouldn't it be easier using a template and a custon attribute for the model (instead of ng-bind) ?
And the answer is yes !
Actually that's just my opinion, but here is what I did by modifying your code http://jsfiddle.net/DotDotDot/m4gbZ/73/
I let you have a look, I had to change some parts (the ng-click doesn't work well on the textarea so I put this behavior on the Save button) but I think this is almost what you wanted. On code side, I modified the HTML to avoid calling ng-bind, using a custom scope variable which will be caught in the directive :
<span inline-edit ff="data_copy.user.comment">First</span>
On the directive side, I got rid of all the compile/controller stuff, and I added a template
return {
restrict: "A",
template:'<div><span ng-hide="editMode" ng-click="editMode=!editMode">{{aModel}}</span><textarea ng-show="editMode" ng-model="aModel"></textarea> <button ng-click="save()">{{getLabel()}}</button></div>',
replace:true,
scope: {aModel:'=ff'},
link: function(scope, element, attrs){
console.log(element)
scope.editMode = true;
scope.save = function(){
console.log("Saving...");
scope.editMode=!scope.editMode;
};
scope.getLabel=function(){
if(scope.editMode)
return "Save";
else
return "Change";
}
console.log(element.parent().contents());
}
}
Why ? The template, because angular will compile it itself without any intervention.
I added replace:true to replace the line, but it's optionnal
The scope part is more important. scope: {'=ff'} tells angular that I want to use an isolated scope, and I want the scope.aModel value to be bound with the ff variable passed in the HTML.
The '=' means that the modifications will be evaluated from the parent scope, and every modification will be reflected in the parent and in the directive
I replaced your controller and your compile function (no element to compile, and adding function can be done here instead of a dedicated controller) by a linking function containing the functions needed. As I said before, I added the editMode change behavior to the Save button, so I added a bit more code, but it's not the main point, I think you may have to change things here to reflect your expected behavior
I hope this will help you, as I don't really answer your question, but I think you could also explore this way
++

Angularjs passing object to directive

Angular newbie here. I am trying to figure out what's going wrong while passing objects to directives.
here's my directive:
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
and this is the template where I call the directive:
<div walkmap="store.walks"></div>
store.walks is an array of objects.
When I run this, scope.walks logs as undefined while scope logs fine as an Scope and even has a walks child with all the data that I am looking for.
I am not sure what I am doing wrong here because this exact method has worked previously for me.
EDIT:
I've created a plunker with all the required code: http://plnkr.co/edit/uJCxrG
As you can see the {{walks}} is available in the scope but I need to access it in the link function where it is still logging as undefined.
Since you are using $resource to obtain your data, the directive's link function is running before the data is available (because the results from $resource are asynchronous), so the first time in the link function scope.walks will be empty/undefined. Since your directive template contains {{}}s, Angular sets up a $watch on walks, so when the $resource populates the data, the $watch triggers and the display updates. This also explains why you see the walks data in the console -- by the time you click the link to expand the scope, the data is populated.
To solve your issue, in your link function $watch to know when the data is available:
scope.$watch('walks', function(walks) {
console.log(scope.walks, walks);
})
In your production code, just guard against it being undefined:
scope.$watch('walks', function(walks) {
if(walks) { ... }
})
Update: If you are using a version of Angular where $resource supports promises, see also #sawe's answer.
you may also use
scope.walks.$promise.then(function(walks) {
if(walks) {
console.log(walks);
}
});
Another solution would be to add ControllerAs to the directive by which you can access the directive's variables.
app.directive('walkmap', function() {
return {
restrict: 'A',
transclude: true,
controllerAs: 'dir',
scope: { walks: '=walkmap' },
template: '<div id="map_canvas"></div>',
link: function(scope, element, attrs)
{
console.log(scope);
console.log(scope.walks);
}
};
});
And then, in your view, pass the variable using the controllerAs variable.
<div walkmap="store.walks" ng-init="dir.store.walks"></div>
Try:
<div walk-map="{{store.walks}}"></div>
angular.module('app').directive('walkMap', function($parse) {
return {
link: function(scope, el, attrs) {
console.log($parse(attrs.walkMap)(scope));
}
}
});
your declared $scope.store is not visible from the controller..you declare it inside a function..so it's only visible in the scope of that function, you need declare this outside:
app.controller('MainCtrl', function($scope, $resource, ClientData) {
$scope.store=[]; // <- declared in the "javascript" controller scope
ClientData.get({}, function(clientData) {
self.original = clientData;
$scope.clientData = new ClientData(self.original);
var storeToGet = "150-001 KT";
angular.forEach(clientData.stores, function(store){
if(store.name == storeToGet ) {
$scope.store = store; //declared here it's only visible inside the forEach
}
});
});
});

Resources