Angular understanding the link function - javascript

i am trying to find out when and how to use the link function in angular directives.
Say i have the following directive:
app.directive("lbArticle", function() {
return {
restrict : "E",
templateUrl: 'tpl/directives/information/article.html',
scope: {
article: '='
},
link: function(scope,element, attr){
scope.info = attr.article;
}
};
});
Now passing an object to the article attachment of the HTML
<lb-article article='{{myObject}}'> </lb-article>
When this happens and the directive is rendered it should set a variable called info equal to myObject
So if myObject looked like this:
myObject{name: 'Hello', created: '2015-04-04'; }
Then the following should display these variables:
my directive html
<span class="block text-ellipsis">{{info.name}}</span>
<small class="text-muted">{{info.created | fromNow}}</small>
However this does not work?
As far as i can read for the documentation the link function should be used for DOM manipulation and also for setting variables that might be rendered before the actual directive?

This line scope.info = attr.article; is redundant, since you have access to article via the two-way binding =article. So you can replace all occurrences of info with article in the template, since that is available in scope. You also need to remove the curly brackets from <lb-article article='{{myObject}}'> </lb-article> for two-way binding to work, since you want a reference to the object.
There's a nice, easy to follow article on directives, which covers most of these concepts (link/complile functions, two/one-way binding, scopes, etc.).

Use
<lb-article article='myObject'> </lb-article>
and try now.

Related

Prevent controller from creating new scope object

I am passing a custom scope object to the $compile and creating a custom template. If I apply a directive on the elements inside the template, scope that is changing is the one that is passed to the $compile, and that's really what I wanted.
However, I just thought that it might be good to also have a controller on some elements inside the template,
<div ng-controller="controllerName" >
</div>
but ng-controller doesn't set data on the passed scope but creates its own and uses that one. Is there a way to make ngController to use existing scope and not create a new one ?
We create our controllers and wrap them in factories to make them accessible. We apply or controllers through directives (also going away). This gives you a controller that is scoped to the directive, which has better control for scope, this works for us as the directives where we do this for are usually components.
I don't know if this will be an option given the road you are down now. I would suggest trying to stop using ng-controller. You may want to look at angular 2 now just to keep it in mind as a migration path, it is coming in the fairly near future. They have removed ng-controller, a lot of what they are doing in angular 2 can be done now.
This is a good resource on why these things are a bad idea
https://www.youtube.com/watch?v=gNmWybAyBHI&t=9m10s
If you look at the source code for ng-controller, you will see it is very simple:
var ngControllerDirective = [function() {
return {
restrict: 'A',
scope: true,
controller: '#',
priority: 500
};
}];
You can actually create an almost identical alternate directive that just defines scope: false (or omits the scope key altogether, same thing):
app.directive('controllerNoScope', function () {
return {
restrict: 'A',
scope: false,
controller: '#',
priority: 500 // same as ng-controller
}
});
(You may want to give it a better name).
See this Plunkr for a demo that shows the scope has the same $id as the outer one, meaning it is the same scope.

Angular Directive with Options, Callbacks like jQuery Widgets?

I'm creating a custom control. I'm thinking of using Angular Directives.
But I see that directives require you to initialize your widget with html for example:
<mywidget></mywidget>
And in order to pass variables you also do this in html:
<mywidget title="my widget"></mywidget>
But I don't want this. I want to the initialization of my object programatically. Think jQuery widgets:
$('#container').mywidget({
title: 'my widget',
somecallback: function(event, ui){
// do stuff
}
});
Basically, I'd like to set options and set callbacks programatically. Is this available using angular directives or am I barking up the wrong tree?
Yes. You can not only pass just a string to a directive, but any kind of object or functions that are "programmatically" defined in a controller. For instance:
Your HTML:
<mywidget callback="someFunction()"></mywidget>
Your directive:
directive('mywidget', function() {
return {
scope: { callback: '&' }
};
});
Your upper controller:
$scope.someFunction = function() {
// programatically do something
}
Angular is declarative not imperative by nature which means you don't get to do that in an easy way and is for a good reason(separation of concerns)
making your directives clearly bound to your html helps readbility, maintenability and testability, now, you still get to configure your directive using the different binding types in your directives scope definition you can do this inside your directive
scope:{
configs:'='
}
which will create 2 way data binding with the config object which you can use to configure your directive and comunicate with the config object owner, or you can do
scope:{
configs:'&'
}
and then on your direcitves controller or link function do
scope.configs=scope.configs();
to get the object and then you can use it to configure, notice this doesn't create a 2way binding wit the object but it returns and object representation instead some thing similar goes to callback functions or events callback using the 2 way biding operator '='(not recommended but possible) or you can set it using the evaluation operation '&'
scope:{
onClick:'&'
}
and then on your controller/link function do
scope.onClick=scope.onClick();
and use it as
scope.onClick(params)
then when you declare your directive you do
more on this you can find here
https://docs.angularjs.org/guide/directive
https://docs.angularjs.org/api/ng/service/$compile

AngularJs: Binding a property of a scope object to a directive

I'm somewhat new to AngularJs, so forgive me if this is a newb question, but I've looked around a bit and haven't been able to figure this out.
I'm trying to send an attribute of an object into a directive and I'm not quite sure why this isn't working.
I've got a scope variable that's an object, something like:
$scope.player = {name:"", hitpoints:10};
In my HTML, I'm attempting to bind that to a directive:
<span accelerate target="player.hitpoints" increment="-1">Take Damage</span>
In my directive, I'm attempting to modify player.hitpoints like this:
scope[attrs.target] += attrs.increment;
When I trace it out, scope[attrs.target] is undefined, even though attrs.target is "player.hitpoints." When I use target="player", that traces out just fine but I don't want to have to manipulate the .hitpoints property explicitly in the directive.
Edit: I've made a jsfiddle to illustrate what I'm trying to do: http://jsfiddle.net/csafo41x/
There is a way to share scope between your controller and directive. Here is very good post by Dan Wahlin on scope sharing in Directive - http://weblogs.asp.net/dwahlin/creating-custom-angularjs-directives-part-2-isolate-scope
There are 3 ways to do so
# Used to pass a string value into the directive
= Used to create a two-way binding to an object that is passed into the directive
& Allows an external function to be passed into the directive and invoked
Just a very basic example on how the above mentioned scope are to be used
angular.module('directivesModule').directive('myIsolatedScopeWithModel', function () {
return {
scope: {
customer: '=' //Two-way data binding
},
template: '<ul><li ng-repeat="prop in customer">{{ prop }}</li></ul>'
};
});
There are a number of things going on here:
#1 - scope
Once you define your isolated scope (along the lines of #Yasser's answer), then you don't need to deal with attrs - just use scope.target.
#2 - template
Something actually needs to handle the click event. In your fiddle there is just <span class="btn"...>. You need ng-click somewhere. In your case, you probably want the directive to handle the click. So modify the directive's template and define the click handler in the directive's scope:
...
template: "<button class='btn' ng-click='click()'>Button</button>",
link: function(scope, element, attrs)
{
scope.click = function(){
scope.target += parseInt(attrs.increment);
}
}
...
#3 - transclude
Now, you need to get the contents of the directive to be the contents of the button within your directive's template. You can use transclude parameter with ng-transclude - for location, for that. So, the template above is modified to something like the following:
...
template: "<button class='btn' ng-click='click()'><div ng-transclude</div></button>",
transclude: true,
...
Here's your modified fiddle

AngularJS $parse not working

I am trying to understand $parse, based on the documentation. But I am having trouble to get my test code working. Am I using $parse service the right way?
The main part of the code is:
app.directive('try', function($parse) {
return {
restrict: 'E',
scope: {
sayHello: "&hello"
},
transclude: true,
template: "<div style='background:gray;color:white'>Hello I am try: <span ng-transclude></span><div>",
link: function($scope, $elem, $attr) {
var getter = $parse($attr.sayHello);
// var setter = getter.assign;
$elem.on('click', function() {
getter($scope);
$scope.$apply();
});
}
};
});
See my code at: http://plnkr.co/edit/lwV5sHGoCf2HtQa3DaVI
I haven't used the $parse method, but this code achives what you are looking for:
http://plnkr.co/edit/AVvxLR4RcmWhLo8eqYyd?p=preview
As far as I can tell, the $parse service is intended to be used outside of an isolate scope.
When you have an isolate scope, like in your directive, you can obtain a reference to the parent scope's function using the 'sayHello': '&' as proposed in Shai's answer. The $parse service might still work as expected even with an isolate scope, if you are able to pass in the parent scope instead of the directive's scope when calling getter($scope), but I haven't tested that.
Edit: This is indeed the case - using getter($scope.$parent) works fine. When an isolate scope is used in your directive, the $scope variable no longer refers to the correct context for the getter function returned by the $parse service. Access the correct one by using $scope.$parent.
However, if you are avoiding an isolate scope, your approach works well. Try removing the scope: { ... } section out of your directive definition entirely and you'll see it works fine. This is handy if you are creating a directive for event binding that might be applied to an element in conjunction with another directive that has an isolate scope, say a dragenter directive (which isn't provided by Angular). You couldn't use Shai's method in that case, since the isolate scopes would collide and you'd get an error, but you could use the $parse service.
Here's an updated plunker with the scope removed from the directive definition: http://plnkr.co/edit/6jIjc8lAK9yjYnwDuHYZ

AngularJS : Modify model of parent scope in a directive with '=xxx' isolate scope?

I have an Angular directive which permit to render an user, and create a link to view the user profile, declared as:
.directive('foafPerson', function() {
return {
restrict: 'E',
templateUrl: 'templates/person.html',
scope: {
personModel: '=',
onClickBindTo: '=',
onPersonClick: '&'
}
};
As you can see, I'm trying 2 solutions to be able to visit and load the full user profile: onClickBindTo and onPersonClick
I use it like that to render a list of persons + their friends:
// display the current user
<foaf-person person-model="person" on-person-click="changeCurrentProfileUri(person.uri)" on-click-bind-to="currentProfileUri"></foaf-person>
// display his friends
<div class="profileRelationship" ng-repeat="relationship in relationships">
<foaf-person person-model="relationship" on-person-click="changeCurrentProfileUri(relationship.uri)" on-click-bind-to="currentProfileUri"></foaf-person>
</div>
On the template, I have a link that is supposed to change an attribute of the controller (called currentProfileUri)
<a href="" ng-click="onClickBindTo = personModel.uri">
{{personModel.name}}
<a/>
I can see that the controller scope variable currentProfileUri is available in the personTemplate.html because I added a debug input: <input type="text" ng-model="onClickBindTo"/>
Unfortunately, when I modify the input value, or when I click on the link, the currentProfileUri of the controller is not updated. Is this normal or am I missing something?
With the other method it seems to work fine:
<a href="" ng-click="onPersonClick()">
{{personModel.name}}
<a/>
So to modify a model of the parent scope, do we need to use parent scope functions?
By the way, passing an expression with &, I tried another solution: not using a function declared in the controller scope:
<foaf-person person-model="relationship" on-person-click="currentProfileUri = relationship.uri"></foaf-person>
How comes it does not work?
My controller has nothing really fancy:
$scope.currentProfileUri = 'https://my-profile.eu/people/deiu/card#me';
$scope.$watch('currentProfileUri', function() {
console.debug("currentProfileUri changed to "+$scope.currentProfileUri);
loadFullProfile($scope.currentProfileUri);
})
$scope.changeCurrentProfileUri = function changeCurrentProfileUri(uri) {
console.debug("Change current profile uri called with " + uri);
$scope.currentProfileUri = uri;
}
I am new to Angular and read everywhere that using an isolate scope permits a two-way data binding with the parent scope, so I don't understand why my changes are not propagated to the parent scope and my debug statement doesn't fire unless I use the scope function changeCurrentProfileUri
Can someone explain me how it works?
In your example the scopes hierarchy is the following:
controller scope
ng-repeat scope
foaf-person scope
so when you declare two-way binding for 'currentProfileUri' it is actually bound to the scope created by ng-repeat, not by the controller, and when your code changes value of onClickBindTo then angularjs executes 'currentProfileUri = newValue' in the ng-repeat scope.
Solution is to use objects instead of primitive values for two-way bindings - in this case scopes inheritance always work in proper way. I mean something like that:
// display the current user
<foaf-person person-model="person" on-click-bind-to="currentProfile.uri"></foaf-person>
// display his friends
<div class="profileRelationship" ng-repeat="relationship in relationships">
<foaf-person person-model="relationship" on-click-bind-to="currentProfile.uri"></foaf-person>
</div>
I've prepared a js-fiddle which illustrates this behavior

Categories

Resources