Angularjs directive : accessing sibling elements in link function - angularjs

I've googled a lot to find an answer to a question which seems fundamental to me, but surprisingly with no success. The official doc left me without answers too.
So the simple question is : how to access sibling elements inside the link function of a directive ?
I have a view with an ng-repeat. My directive bind a click on each of the ng-repeat elements :
link: function (scope, element) {
element.bind('click', function () {
// how to access element.siblings here ?
...
Thanx.

If you are working with jQuery you can use the .siblings() function. If you only have an angular.element, you can access the subsequent siblings with the .next() function.
However, since you are using angularjs, I recommend to rely on the model for such kind of interactions instead of accessing DOM elements the "jQuery" way. If you provide more detail on what you are trying to do, somebody might suggest a better way (from a design perspective) to achieve your goal.

Related

Find element without using angular.element and jQuery

I have an Angular controller which uses pure Angular (no jQuery or Javascript). My problem is that I need to find and element without using jQuery or Javascript. I searched a lot on net but could not find any helpful answer.
The solution I found was to use angular.element, but it is not a good idea to use it.
Can anybody help me how to find element without using jquery.element or angular.element?
Thanks in advance.
If jQuery is available, angular.element is an alias for the jQuery function. If jQuery is not available, angular.element delegates to Angular's built-in subset of jQuery, called "jQuery lite" or jqLite.
From here
PS: if you still want to use something other than this look here
Oh, one other edit: you can't actually use angular without javascript, since it is javascript framework..
I would recommend is to create a directive. The element is handed to you in the linking function:
link: function(scope, elem, attr) {
//elem is the dom element that contains the directive...your target object
}
If you do it like this you dont have to create a selector that would look at the entire dom from your controller. In your directive you could also do document.querySelector(".myclass") if the elem object doesn't work for your use case for whatever reason....but I think it should.

Why element is not available in directive's link function

You have probably heard it as many times as I have. "Do all your DOM manipulation in directives". But no one ever seems to say what could happen if you actually do DOM manipulation outside a directive in Angular.
I have a problem that I managed to reproduce in this Plunk
I have made a very simple directive that just outputs the element to the console.
app.directive('dirre', function(){
return {
link: function(scope, element, attrs){
console.log({message:"dirrens linkFn", element: element, count: element.length})
}
}
});
I have two identical jquery UI accordions, the only difference is the way they are called. One is called in a controller and the other one in a directive. Calling accordion from a controller is of course something bad.
As you can see if you run the application there is a situation where one of the dirre-directives does not seem to have an element but there are no errors.
The same thing happens in a big application I'm working with right now. The problem seems to be that someone in our team decided to call Jquery UI's accordion in a controller and not in a directive.
I haven't been able to step through the code to see what actually happens but I strongly suspect that the DOM is modified while Angular is compiling and something goes wrong.
Is this a plausible explanation?
Is this an example of what can go wrong if you do DOM manipulations outside a directive?
The controller and the directive links function are called asynchronously.
Usually you can see directives being built before the main Controller complete. When the controller terminates, the directives update their watched variable (ngModel, $watch(something)...). Basically this is done with promises.
The link/compile function however is not called again. You have to compile, watch, apply the new DOM. Which basically means writing the similar code to angularjs.

Exposing directive controller to parent controller

I want to expose some of my directive's functionality through its controller (think a public API for this directive).
return {
restrict: 'E',
scope: {},
controller: function($scope) {
this.method1 = ...;
this.method2 = ...;
},
controllerAs: 'dir',
link: function (scope, element, attrs) { ... }
}
Then in my parent controller or template call dir.method1 to get stuff accomplished inside the directive. Any ideas if this is possible as of Angular 1.3?
I'd like to refrain from event passing or even function passing, I have heard this is possible although I have never seen an implementation of this.
It is possible, but your issue isn't to figure out how to get the API out. It's how to get TO it from the parent. You're creating an isolate scope through your use of the 'scope' option. You're also making an element-type directive, so I'm guessing you're doing something like this:
<my-parent>
<my-child></my-child>
</my-parent>
where <my-parent> is the parent directive, and <my-child> is the directive with the API you want to expose.
The real question is what you're trying to achieve here. There is totally a way to do what you're asking. Just because the scope is isolated doesn't mean you can't get to it. You can just iterate through the parent $scope's $$childHead/etc list to find the child whose API you want to access. Anything you define in the child like this:
$scope.myApiFunction = function() {
};
will be visible here. (Things you put into 'this' will not - use the $scope storage bucket instead.)
That means if you only had ONE child you could do something like this from the parent controller:
$scope.$$childHead.myApiFunction();
Simple. Also, very crude. There are lots of problems here: what if you have many children? What if this child with its API ends up one level down? Etc. It's breaking all kinds of OO patterns and it's going to get messy, fast.
Your question is very abstract - it might be good if you updated it with an exact example. Without that, let me guess at your goal. There are two ways to do something "like this" that are encouraged within Angular:
Services. Whenever you say "API", think Service first. A service is a singleton (automatically) so it's tailor-made for creating APIs. And services can use the Factory pattern to return objects of a type, so THOSE are tailor made for doing things like having a manager service handle, say, a buddy list in an IM client, with API methods for creating, removing, and finding buddies.
Items that add "optional" functionality to their parents when they're defined. Let's say we have three possible types of tooltips: tooltips that have a hover effect, those that have a click effect, and those that are triggered by a "walkthrough" system in some order. For this kind of thing, the easy thing to do is just reverse the API, like this:
Parent Controller:
$scope.tooltipHandler = {
showTooltip: function() {},
hideTooltip: function() {}
};
Child Controller:
$scope.$parent.tooltipHandler = {
showTooltip: function() {
// Do some real work
},
hideTooltip: function() {
// Do some real work
},
}
What happens here is if there's no tooltip defined, when the parent runs its walkthrough, nothing happens. If you add the blue tooltip display module, when the parent runs its walkthrough now, it's going to show blue tooltips.
Make sense?
I arrived here looking for a similar response. So far the best that I can figure is to do what Angular does with ngForm.
In the documentation clearly states
If the name attribute is specified, the form controller is published onto the current scope under this name.
This basically makes the form controller accessible from anywhere.
If you have the following DOM
<div ng-controller="MyCtrl as parentCtrl">
<form name="parentCtrl.frmCtrl">
<my-child-directive>
</form>
</div>
You can use require: 'ngForm' in my-child-directive to get access from an inside directive. If you are in the parent controller you can access it trough the frmCtrl variable.
Not sure if this is best practice. In ngForm the name attribute works well, but I don't even know how to call such an attribute for a custom directive.
Thats why I arrived here, I wanted to know if this is "The Angular way" and what types of convetions are on the subject.
Hope it helps!

Creating an AngularJS Directive for jQuery UI Button

Update: Fiddle w/ full solution: http://jsfiddle.net/langdonx/VXBHG/
In efforts to compare and contrast KnockoutJS and AngularJS, I ran through the KnockoutJS interactive tutorial, and after each section, I'd rewrite it in AngularJS using what little I already knew + the AngularJS reference.
When I got to step 3 of the Creating custom bindings tutorial, I figured it would be a good time to get spun up on Angular Directives and write a custom tag. Then I failed miserably.
I'm up against two issues that I haven't been able to figure out. I created a new Fiddle to try and wrap my head around what was going on...
1 (fiddle): I figured out my scoping issue, but, is it possible to just passthrough ng-click? The only way I could get it to work is to rename it to jqb-click which is a little annoying.
2 (fiddle): As soon as I applied .button() to my element, things went weird. My guess is because both Angular and jQuery UI are manipulating the HTML. I wouldn't expect this, but Angular seems to be providing its own span for my button (see line 21 of the JavaScript), and of course so is jQuery UI, which I would expect. I hacked up the HTML to get it looking right, but even before that, none of the functionality works. I still have the scope issue, and there's no template binding. What am I missing?
I understand that there's an AngularUI project I should be taking a look at and I can probably pull off what I'm trying to do with just CSS, but at this point it's more about learning how to use Directives rather than thinking this is a good idea.
You can create an isolated scope in a directive by setting the scope parameter, or let it use the parent scope by not setting it.
Since you want the ng-click from parent scope it is likely easiest for this instance to use the parent scope within directive:
One trick is to use $timeout within a directive before maniplulatig the DOM within a templated directive to give the DOM time to repaint before the manipulation, otherwise it seems that the elements don't exist in time.
I used an attribute to pass the text in, rather than worrying about transclusion compiling. In this manner the expression will already have been compiled when the template is added and the link callback provides easy access to the attributes.
<jqbutton ng-click="test(3)" text="{{title}} 3"></jqbutton>
angular.module('Components', [])
.directive('jqbutton', function ($timeout) {
return {
restrict: 'E', // says that this directive is only for html elements
replace: true,
template: '<button></button>',
link: function (scope, element, attrs) {
// turn the button into a jQuery button
$timeout(function () {
/* set text from attribute of custom tag*/
element.text(attrs.text).button();
}, 10);/* very slight delay, even using "0" works*/
}
};
});
Demo: http://jsfiddle.net/gWjXc/8/
Directives are very powerful, but also have a bit of a learning curve. Also in comparison of angular to knockout, angular is more of a meta framework that in the long run has far more flexibilty than knockout
Very helpful reading for understanding scope in directives:
https://github.com/angular/angular.js/wiki/The-Nuances-of-Scope-Prototypal-Inheritance

How do I target only certain elements with the same directive, without changing the markup?

I've been researching this for a few hours now.
Let's say I have a jQuery selector of $('#bottom .downloads .links a').click.....
How can I do the same type of thing in an Angular directive?
This is what I have so far and it works, but for all tags on the page.
angular.module('directives', []).directive('a', function(mongoDB){ //Don't need ['customServices'], it can just be [] to use mongoDB
return {
restrict : 'E',
link : function(scope, element, attrs){
element.on('click', function(){
//But this gets called for all links on the page
//I just want it to be links within the #bottom .downloads .links div
//I wanted to use a directive instead of ng-click="someMethod()"
});
}
});
Is there a way to target this directive to only a certain div? I guess I could change the restrict to 'C' or 'A' and add an attribute to the links, but I was wondering if I could still layout the front end like I currently am used to with my jQuery selectors.
There is a pretty significant philosophical difference between AngularJS and jQuery. In jQuery, everything is in the DOM - including your data - and you do everything through DOM transformations. AngularJS, on the other hand, has separation of concerns built in: models, views, controllers, services, etc., are all separate. We use controllers to glue code together, but each component knows nothing about the other components.
So whereas in jQuery, one might use a selector to find all links matching a certain pattern and then add a certain functionality to it (say a click handler), in AngularJS, the HTML is the "offical record". Instead of abstracting away the attachment of a click handler into a JavaScript function, it is put right into the markup:
<a ng-click="doWhatever()">Click me!</a>
In this case, doWhatever is a method on the scope for that part of the page, probably set in your controller:
$scope.doWhatever = function () {
console.log("Hello!");
}
So the way you are approaching the problem is not going to work in AngularJS. Instead, you need to look at directives not like jQuery selectors with a function, but as an extension of HTML. You ask yourself, "what does HTML not do out of the box that I need it to?" Your answer is your directive.
But AngularJS already has a built-in directive for click handlers (the ngClick used above).
Angular already has an a directive, so you probably shouldn't create your own.
In an Angular world, to "target only a certain div" (well, <a> within the div) we declaratively target that <a> with a directive, rather than use CSS-like selectors. So yes, restrict to 'A' and add an attribute to the <a> would be best:
<a ... target-this-one>...</a>
I personally think this reads better. Looking at the HTML it is clear which <a>s have special/additional functionality.
As #Josh pointed out, you would only need to do this if ng-click isn't sufficient for your needs.

Resources