Syntax In Directive - angularjs

I followed this YouTube on Directives
https://www.youtube.com/watch?v=0r5QvzjjKDc
and it was very good, imo. Got through following along successfully right up until the very end (link). The debugger is telling me
TypeError: element.click is not a function
I've looked at this seven ways to Sunday and I'm just not seeing where it's not matching what he's got. Is the syntax error jumping out at you?
Thanks.
angular
.module('app.directives.contactCard', [])
.directive('contactCard', function() {
return {
/* choices are E-Element, A-Attribute, or C */
restrict: 'E',
scope: {
friend: '=',
title: '=',
},
replace: true,
transclude: true,
templateUrl: "contactCard.html",
link: function(scope, element, attrs) {
element.click(function() {
alert('click');
});
},
controller: function($scope) {
console.log($scope.friend);
}
}
})

This looks pretty good, just a slight problem with the element syntax. The error is indicating there is no such function (i.e. click()) on element.
Try using the following, which uses bind:
element.bind('click', function() {
alert('click');
})
I replicated your directive here: http://plnkr.co/edit/SX0zwYipydVvo6EMVfzE

Overall you're on the right track, but the hypothetical click function like that is more reminiscent of the jQuery way of doing things than the Angular way. Typically, if it feels like you're trying to manipulate the features of the DOM through JS, you're thinking in jQuery instead of Angular. I realize that the tutorial utilized it, but it somewhat defeats the purpose of Angular, and I think hides a lot of its real power.
Common conventions for click-functions would be to assign a function to $scope in the directive's controller (or to the directive itself), then refer to it with ng-click in the directive's template.
If you can update your question to include the template, I can give a more specific example. I expect it'll be extremely succinct.

Related

simple directive to show static text

Our app is not using angular 1.3 (yet, we have to check the dependencies before updating), but I need to use One-time binding from 1.3 in some simple text attributes.
Wrote this directive to accomplish that
return {
scope: {
'text': '='
},
restrict: 'AE',
template: '{{ text }}',
link: function link($scope, element, attrs) {
}
};
And it is used like this
<span static-text text="friend.name">
The problem is that it still adds a watch on {{ text }} (screenshot from Batarang)
Is there a simple way of displaying a text without the permanent watch? (looked at this solution but seems to be too much just for showing some text).
EDIT: I ended up using the solutions proposed by #arturgrzesiak and #PSL, #arturgrzesiak's solution was used when no async proccesing was present, and for the other scenarios I used #PSL's. Both solutions work, but I'll accept #PSL's since it covers more scenarios.
There are some advantages that you get by having a watch. One example is in your actual code you are setting the data asynchronously which means the bound variable gets updated during the next digest cycle. But it's overkill (So bindonce or other watch removal libraries or 1.3 two-way binding exist) in some case. Here is one thing you can do, just use a watch until you get the data and then remove it once you have got it and set the html manually from the directive.
return {
restrict: 'AE',
link: function link($scope, element, attrs) {
var unwatch = $scope.$watch(attrs.staticText, function(val){ //Set up temp watch
if(val){
unwatch(); //Unwatch it
element.html(val); //Set the value
}
});
}
};
and just use it as
<span static-text="friend.name">
The solution is a bit more convoluted than what I proposed in the comment.
app.directive('once', function($parse){
return function(scope, element, attrs){
var parsed = $parse(attrs.once)(scope);
element.html(parsed);
}
});
DEMO

Simple Angular Directive

I'm attempting to write my first Angular directive to add pagination functionality to my app.
I'm keeping it simple for now and want to have the directive just share the scope of the controller.
In my controller, I have a scoped var called:
$scope.pages
Inside my directive's linking function, I can do:
console.dir($scope)
to see the contents of the controller's scope object, but if I try to access the pages property, it comes back as undefined.
What am I missing?
Thanks in advance.
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
scope: false,
link: function($scope, el, attrs){
console.dir($scope.pages); //returns undefined
el.text('hello world');
},
}
});
Edit: I'm aware there's pagination modules out there, but my needs are modest and I thought building my own would be an easy way to start learning how to build directives.
The problem you have is directives linking functions are run before controllers are, so you typically can't access scope values during this linking phase. There are a couple other ways to do this. The most immediate way to solve your problem is with $scope.$watch. So something like this should work:
$scope.watch('pages', function(pages) {
console.dir(pages);
});
This works just fine for lost of circumstances. I would also suggest decoupling your controller from the scope it is declared in a bit by using an attribute to pass in an expression (property name basically) for your watch. A simple example might look like this:
<div my-directive="myProperty">
link: function($scope, el, attrs) {
$scope.watch(attrs.myDirective, function(value) {
//do something you need to do with the value here
});
}
However as you add complexity to your directive you may want to give your directive it's own controller to manage state without directly calling watches. This controller can either use a child scope belonging to the directive itself with scope: true or just use the parent scope with scope: false. Your choice will likely depend upon need. Then you could have:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
controller: ['$scope', '$element', '$attrs', function($scope, $element, $attrs) {
console.dir($scope.pages);
}],
link: function($scope, el, attrs){
$element.text('hello world');
}
}
});
By default, the directive shares its scope with the controller. To do that, you must not having a "scope" attribute in your object, like that:
myDirectives.directive("mdPagination", function(){
return {
template: '',
restrict: 'A',
priority: 1000,
link: function($scope, el, attrs){
console.dir($scope.pages);
el.text('hello world');
},
}
});
#Sandro is right. You set the scope property in your directive to false which means that the scope will not inherit (or share) its properties with the controller. If you remove the false, it should work as you intend it.
The docs for how to use $scope are here: http://code.angularjs.org/1.2.0-rc.3/docs/api/ng.$rootScope.Scope
The developer guide for creating directives has a lot of good info about how to setup your directive so that it gives you the scope you want. Those docs are here: http://code.angularjs.org/1.2.0-rc.3/docs/guide/directive
Hope that helps.

What's the generalizable way to get values out of a directive?

I'm writing a little threaded discussion board in angular. I want to use hallo.js for my inline editor (or something similar, the problem doesn't actually depend on hallo).
Here's the relevant snippet from the template
<div ng-show="post.editing" ng-bind-html="post.body" edit-hallo class="col-xs-8"></div>
<div ng-show="!post.editing" ng-bind-html="post.body" class="col-xs-8"></div>
Here's my directive:
Appl.directive('editHallo', function () {
return {
restrict: 'AC',
scope: true,
link: function(scope, element, attr) {
element
.hallo({
plugins: {
'halloformat': {"bold": true, "italic": true, "strikethrough": true, "underline": true},
'halloheadings': [1,2,3],
'hallojustify' : {},
}
});
element.bind('hallomodified', function(event, data) {
scope.post.body = data.content;
});
}
};
});
This all works just fine, but the hack is right there at the end - when there's a hallomodified event, I manually say, scope.post.body = data.content which not only feels like a hack, it means this only works when there's a post.body item that I'm editing, and therefore doesn't work well if I want to repurpose this for the profile editor or whatever.
So my question is: how should I refactor this so that the relevant two-way binding works? I tried a few things that seemed obvious, such as putting a app-model="post.body" in the div, and then doing an isolate scope with =, but that wasn't getting me anywhere. Ideally, I'd pass in the appropriate model using an ng-model directive, but that seems to have changed sometime between when all the directive examples I found online were created and angular 1.2.0.
There's been some time I don't use AngularJS.
But I think the best way would be to change the scope to something like:
scope:{ngModel:'='}
or
scope:{attribute:'='}
That way it should make a two data binding. One with ng-model on first case, or attribute on second.
Then you can just do this when event happens:
scope.$apply(function(){
scope.ngModel=newValue;
})
The apply will be needed so angular can call digest cycle again and update the view.
More info, I think this can help:
http://docs.angularjs.org/guide/directive

Calling $compile for a directive, outside of the directive

From all the examples I've seen this should work: (See EDIT, I answered my own question.)
.directive('ssmViewport', ['$compile', 'ssmLayoutMan', function($compile, layoutManager) {
return {
restrict: 'AE',
replace: false, // TODO: remove if false is the default.
link: function (scope, element, attrs) {
layoutManager.renderView = function (view) {
console.log('this text does appear in the log...');
element.append('This text should appear...'); // but it doesn't appear
// these two commented lines are what I'm really trying to do.
//element.append(view.template);
//$compile(element.contents())(scope);
}
};
}
};
}])
Elsewhere in the code I call:
layoutManager.renderView({template: '<div some-custom-directive></div>'});
But alas... the element does not seem to change. I have tried scope.$apply() after my $compile but it just throws an error about already being in the digest, and still doesn't display changes to the element.
EDIT:
I answered my own question. I will mark a correct answer for anyone who can explain why this is so...
The answer is:
ssmViewport is a directive that was defined in some markup that was injected like so:
var html = $compile(templateThatContainsTheSSMViewport)(scope);
element.html(html);
But if I inject it like below, then it will work:
element.html(templateThatContainsTheSSMViewport);
$compile(element.contents())(scope);
I just started learning angular yesterday, so I don't understand the difference yet... Any enlightenment on the issue would be a boon :)

Call controller method from directive without defining it on the directive element - AngularJS

I'm sure there's a simple answer to this that i've just missed.
http://jsfiddle.net/jonathanwest/pDRxw/3/
Essentially, my directive will contain controls that will always call the same method in a controller which is external to the directive itself. As you can see from the above fiddle, I can make this work by defining the attribute with the method on the control directive, but as that method will always be called from the same button within the directive, I don't want to have to define the method to call. Instead the directive should know to call the controller edit method when that button is pressed. Therefore, the definition of the control would be:
<control title="Custom Title" />
How can I achieve this?
Actually I think doing that straightway using $parent is not a recommended way how directives should be defined. Because actually there is no visible dependency on what functions could be called from parent controller, making them little bit harder to re-use.
I do not know actual use case why you need this, but I assume that you use control several times and you do not want to copy-paste bunch of attributes that defines some common behavior.
In this case I would recommend little bit other approach: add some directive-container that will define that behavior, and control will require this directive as dependency:
myApp.directive('controlBehavior', function() {
return {
restrict: 'E',
scope: {
modifyfunc: '&'
},
controller: function($scope, $timeout) {
this.modifyfunc = $scope.modifyfunc;
}
};
});
myApp.directive('control', function() {
return {
restrict: 'E',
require: '^controlBehavior',
replace: true,
scope: {
title: "#"
},
template : '<div>{{title}}<button ng-click="edit()">Edit</button></div>',
link: function(scope, element, attr, behavior) {
scope.edit = behavior.modifyfunc;
}
}
});
Here is a fiddle to demonstrate this approach: http://jsfiddle.net/7EvpZ/4/
You can access the parent scope by using $parent property of the current scope.
http://jsfiddle.net/XEt7D/

Resources