AngularJS directives with children - javascript

I'm trying to make a directive that could be used like this:
<my-directive>
<name>Blah</name>
<age>20</age>
<address>
...
</address>
</my-directive>
My expectation was that I would be able to access the child elements in the link function and get their inner HTMLs. However, that does not work as you don't get the "previous" HTML. I followed the answer in this post and that gives me the previous HTML, but clone is "raw HTML". It's an array of HTML objects, and I can't really search it or anything.
Is there anyway to achieve what I'm trying to do?

Use in directive
transclude:true
And for
children drective
require: '^parent',
For example click here

Should be able to call children function on your element.
var children = element.children();

Related

Angular jqLite .find() will not return any inputs?

EDIT: So it seems to be an issue with selectors? Does jqLite not support selectors or some reduced version of them?
find('input') and find('button') will return results but if I try to filter it with a ":first" or something then it returns no results.
I can't seem to get jqLite's find() to return any child inputs of my div.
I have a $watch on a boolean function that my ng-show uses. So when this div becomes visible I want to apply focus on the div element and then find the first input descendant and focus on that.
example div element that the directive watches:
<div myDirective="function()">
text and stuff
<button>
<another button>
</div>
<div myDirective="function()">
<input>
</div>
this is my helper function in my directive:
function highlightAndFocus(node) {
// focus the div
node.focus();
// get angular's jqlite wrapped element
var task = angular.element(node);
task.addClass('highlight');
// these return empty statements
console.log(task.find('input:first'));
console.log(task.find('button:visible:not("#cancel"):first'));
}
The angular documentation says it finds only by tag name but isn't that what "input" and "button" are?
https://docs.angularjs.org/api/ng/function/angular.element
What's going on here? It seems silly to include jquery just for this one usecase when it seems like it should be supported. I'm printing the var task and I can see the input child elements in the web console.
JQlite is not the same thing as jQuery. If jQuery is available on the page, Angular will use it, but if it isn't, jQlite is used instead.
The docs clearly say that jQlite's .find() only supports lookup by tag names, it doesn't to work with additional selectors (like :first).
You can use the standard DOM APIs instead, namely Element.querySelectorAll(), if you really need it.

trying to add click inside a re-usable directive to a element that may have different classes be different element and may have different text

so here is the desire: I need to add a directive to any html element and pass it an id. This id is then used to populate a method that submits a form that has that id on the page.
Some devs have used a button some have used an anchor tag so I do not want the type of element to be part of the method. It is intended to be highly re-usable.
I already have it working like this:
angular.module("App").directive("submitform", submitFormDirective);
function submitFormDirective(){
return {
restrict: 'A',
scope: true,
link: function($scope, $element, $attrs){
var selector = $attrs.submitform;
$element.on('click', function(){
angular.element(selector).submit();
});
}
}
}
But this uses jquery for the "on" method. I want a fully angular method. Or one that does not "fight" with the framework or use jquery.
The way I am doing it now I only need to apply this attribute to the element. submit-form="#id-of-form" And I would like to keep that functionality becasue it is clean and concise. So I need to be able to add the ng-click to the element dynamically without using the limited template function which would limit the element being effected, inside the submitFormDirective.
The problem is that the $attrs.$set('ngClick', 'submitForm()') runs after the digest and $compile does not seem to work for me. I need to be able to add the click and run the compile on only this element or use a different phase that will happen before the app compile will run. But I have tried using compile to att the attr in conjunction with link adding the method to the scope to no avail. Seems strange that you would not be able to add a event from a directive. I must be missing something. Can someone help?

Getting element width inside directive the Angular way

I have a directive which contains an element inside it with width: 100%. However, the directive must know the clientWidth of that element -- that is, in pixels rather than percent. The element is buried deep in the directive's DOM.
The containing controller may have multiple instances of this directive, so doing document.getElementsByClassName('myclass')[0].clientWidth won't work. Selecting the directive's element works, but the code for that looks similar to element.children().children().children()[32].clientWidth, which doesn't look very stable. What is the right, Angular way of doing this?
Whenever you need to do any DOM manipulation in Angular, directives are the way to go. The "Angular" approach would be to use your directive's link function.
DEMO: http://plnkr.co/edit/83aZUmsRzuTUFotxUYSB?p=preview
Template Use Case:
<div element-width></div>
Directive Example:
angular.module('myApp').directive('elementWidth',function(){
return {
restrict: "A",
link:function($scope, element){
// element param being passed in refers
// to the element the directive is bound to, in this case, the DIV
// do something with the element width here
}
}
});
Hope this helps and let me know if you have any questions.
I figured it out. Although single elements do not have .getElementsByClassName, you can still use .querySelector. So my finished code was:
element[0].querySelector('.myclass').clientWidth

How to dynamically nest a directive element in-between existing parent and child directive elements?

Background:
I am using a custom CMS where I have limited access to the code base. So, in a few cases I plan to make some DOM manipulations using JavaScript.
Problem:
I have a container directive and the container has plain old HTML items, but am not able to mark the items as being directives from the server side. Also, the plain old HTML items contain sub-content that are directives.
Example:
Here is the before:
DIV[container-directive]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Here is what the DOM should look like afterwards:
DIV[container-directive]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
DIV[container-item] <-- This is what needs to be inserted
DIV.some-item-in-html
DIV[some-directive-in-the-content]
...
Question:
Does anyone have suggestions on the best approach to injecting DOM element that are directives in-between a nesting of directives using JavaScript?
Some thoughts:
I think manipulate the DOM in advance of the compilation by angular, but I wonder if there is a way to do this within Angular's framework.
Another option is from the container directive's post-linking function, I could wrap the HTML items in directive elements called "container-item" and then $compile the items manually. So, I tried this but I get an error related to the items already having directives inside with transcluded content. Something about the "ngTransclude" being unexpected. I think this is related to the inner directives already having been processed.
I would go with your first option and manipulate the DOM ahead of angular compilation.
You can do this within a directive that contains the elements that you want to manipulate.
For example:
app.directive('body', function() {
return {
restrict: 'E',
compile: function(element, attr) {
// find the inner element and wrap it
$('.some-item-in-html', element).wrap('<div class="container"></div>');
}
}
});
Parent directives are always compiled before child directives, so you can change the DOM of the children within the compile property, and not have to worry about recompiling or re-linking directives.
[EDIT]
Thanks to Biagio for the following edit.
This method shouldn't be used with a directive with a template because the element would be assigned to the template and not the child elements.
Another alternative is doing the DOM manipulation in a function that runs at the start of the angular lifecycle.
For example:
app.run(function(){
// find the inner element and wrap it
$('.some-item-in-html').wrap('<div class="container"></div>');
});

How to 'destroy' a directive in AngularJS?

I'm using a directive to separate the logic for an entity (for example, a person).
So I have a people service that stores an array of people like:
this.people = ["Person 1", "Person 2", "etc"];
The controller then injects that service, and sends data to a template that contains my directive. So my template looks something like:
<div ng-repeat="person in people">
<person name="person"></person>
</div>
Now, in my directive I have a delete function (called by clicking a button in the directive's template, which looks something like this (People service also injected into directive):
delete People.people[id];
The problem is, the rest of my directive's template remains intact. I want to remove the directive completely upon deletion. How is this possible? I've tried hiding it by using ng-hide and setting a 'deleted' property in the directive's scope to true upon deletion, but the empty (without data) directive template remains. I've looked into scope.$destroy(), element.remove(), etc, but nothing has worked and nothing seems really clear in the documentation...
How can I destroy/remove a directive completely from within itself (i.e. upon calling a delete function)?
Fiddle: http://jsfiddle.net/VSph2/107/
Click delete and the data is gone, but the directive (and template) remains, showing 'name:'. How can I remove the directive completely on delete?
You are deleting object, but empty element in array remains, it is just null reference instead of pointing to an existing object.
Use
this.peopleIds.splice(this.peopleIds.indexOf(personId), 1);
to remove it from array, and that element will not be rendered in for loop anymore.
Reference: How do I remove a particular element from an array in JavaScript?

Categories