alright so, I'm constantly running into this problem where I need to select/access a wrapping container between a parent directive and its child directive.
pseudo code:
<div parent-directive>
<!-- selecting the viewport becomes hard. making an extra directive seems weird -->
<div class="wrapping-div">
<div child-directive ng-repeat="page in pages" ></div>
</div>
</div>
I don't want to use jQuery for selecting wrapping-div by class. And creating a extra directive forwrapping-div element makes the templates so thin and it reads weird? Lastly, using element.children() from parent-directive seems really fragile?
Question:
What is the preferred method of selecting a child element from a template of a directive? Is there Any way to keep it angular w/o making every part a directive?
For a better context, I'm making a basic carousel directive called wa-carousel.
Main.html
wa-carousel is an sort-of API:
<div wa-carousel class="wa-carousel"></div>
wa.carousel.html
this template has a directive: wa-pages.
<div class="wa-carousel-viewport">
<ul class="transition-fast wa-page-collection" ng-style="carousel.pagedOffseter">
<li wa-pages page="page" class="wa-pages" ng-repeat="page in carousel.pages"></li>
</ul>
</div>
As you can see there are 2 wrappers that are only classes not directives wa-carousel-viewport and wa-page-collection.
The wa-page-collection will be using ng-touch but I'd like to control it from wa-carousel link function but I'm forced to use el.children().children() which I dont like.
wa.carousel.js
link: function(scope, el, attrs){
var viewport = el.children().children();
$swipe.bind(viewport, {
'start': function(coords) {}
})
}
**What is the prefered method of selecting wa-page-collection in angular? Is it considered bad practice to do the manipulation inside of wa-carousel's link function? **
Use a data- attribute or similar on the element you want to specifically target. Then:
link: function (scope, el, attrs) {
var viewport = angular.element(el[0].querySelector('[data-my-attr]'));
$swipe.bind(viewport, {
'start': function(coords) {}
})
}
If using jQuery, you can shorten the selector to var viewport = el.find('[data-my-attr]');
Related
Thanks in advance for any help.
I have a jquery tooltip that is styled, but when it is used in an ngRepeat the custom styling of the tooltip is removed and I'm not sure why.
Examples:
Tooltip shows up with correct styling:
<div class="toggle">
<input type="checkbox" id="amount_{{i.ProductCode}}" name="amount" data-ng-model="$parent.amount" value="{{i.ProductCode}}" data-ng-required="1" />
Hello <span class="info-tip" title="World!"></span>
</div>
Tooltip shows up with default styling:
<div data-ng-repeat="i in options.amounts" data-ng-cloak="">
<div class="toggle">
<input type="checkbox" id="amount_{{i.ProductCode}}" name="amount" data-ng-model="$parent.amount" value="{{i.ProductCode}}" data-ng-required="1" />
Hello <span class="info-tip" title="World!"></span>
</div>
</div>
I've spent quite a while looking into it, and from what I can gather from my research it seems to be an ngRepeat scoping issue. However I'm not certain that this is the issue and I'm not sure how to go about fixing it if it is (hence coming here). Ideally I would like to use ngRepeat and maintain my custom tooltip styling.
Any guidance is appreciated, thanks!
The solution was to "refresh" the dynamic elements in the repeater after the repeater has been initialised and rendered.
Create new directive to broadcast when the ngRepeat has finished rendering:
app.directive('onFinishRender', ["$timeout",function ($timeout) {
return {
restrict: 'A',
link: function(scope, element, attr) {
if (scope.$last === true) {
$timeout(function() {
scope.$emit('ngRepeatFinished');
});
}
}
};
}]);
In the controller create a listener for the broadcast that then updates the parent of the tooltip (which in turn updates the tooltip):
$scope.$on('ngRepeatFinished', function (e) {
window.hbf.angular.components.byName("components/Tooltip")
.update($('.parentClass'));
});
Add 'on-finish-render="ngRepeatFinished"' to the ngRepeat in the ascx:
<div data-ng-repeat="i in items" data-ng-cloak="" on-finish-render="ngRepeatFinished">
Reasoning: On the page load event the jQuery binds the custom tooltip class to tooltip function. However, when it's used in the repeater the event is fired after the page load and there is no existing binding. Therefore it is necessary to bind newly created elements to the tooltip again to "refresh" them and have them display.
Basically the jQuery is initialised and rendered, but the ngRepeat is on the client side. Each repeat is dynamically generated with the scope of the child, not the parent, so all original bindings are out of scope and need to be re-binded after the ngRepeat has been created.
If anyone wants more info on how I went about this I posted it on my development blog which you can find here.
I want to find a way to do clean communication between my two sibling directives. I want to implement "insertAtCaret" functionality for a textarea in one directive, to be called from another.
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
text-holder turns into something like this:
<div class='my-class'>
<h3>Enter some text:</h3>
<textarea ng-model='ngModel'></textarea>
</div>
The text-inserter needs to insert stuff into that textarea - what's the cleanest angular-ish way to allow that communication? I want to be able to support multiple instances of that on the page. Should I just create a unique id for each one from a shared service? It seems a little unclean.
You can :
Wrappe your directive in outer DOM element.
create a communication directive on this element.
Use the controller of this directive as an API for communication between the two directives.
Use require from the two directive, to, set, the text.
<div text-com-directive>
<text-holder ng-model='model.text' />
<text-inserter>
<a ng-click='insert("hello")'>Hello</a>
</text-inserter>
</div>
Directive :
directive('textComDirective', function(){
return {
scope:{},
controller: function($scope){
// create functions that will be used to set/use the text inserter.
}
}
});
The only chain between two directives is a variable that is supposed to be updated, this is also used by both directive. The text-inserter directive is sort of like choosing the method to be executed to the text-holder
html
<text-holder ng-model='model.text'></text-holder>
<text-inserter>
<a ng-click='model.insert("hello")'>Hello</a>
</text-inserter>
script.js
var app = angular.module('testapp',[]);
app.controller('appController', function ($scope) {
$scope.model = {text: 'sample', insert: function(a){$scope.model.text = a}};
})
app.directive('textInserter', function () {
return {
restrict: 'E',
trasclude: true // important to keep the content that is defined outside of directive
}
});
Sample
The insert function is set in the controller that is holding the variable to pass to the directive, this way helps us to easy understand what logic should be applied and is going to happen for the model variable in the initiated scope it self.
The more benefit is you can situational change the behavior for some specific instance.
I am adding an element dynamically in Angular. The code is as follows.
myelement.after('<input ng-model="phone" id="phone">');
The element gets added and all works fine. However I think I'm missing a step and Angular does not recognize the new element because when I collect data later, the value of the dynamically added element is not defined.
The reason for adding the element on fly is that the number of inputs is not known from the beginning and user can add as many of them as they want. The actual code is like:
myelement.after('<input ng-model="phone"' + counter + ' id="phone' + counter + '">');
I'm sorry, I should have provided complete sample from the beginning. Please see the following jsfiddle, add some phones (with values) and list them: http://jsfiddle.net/kn47mLn6/4/
And please note that I'm not creating any new directive. I'm only using Angular standard directives, and I prefer not to create a custom directive for this work (unless required).
Assuming that you are adding element to the DOM using directive. You can use built in angular service $compile.
Firstly inject the $compile service to your directive. Then in your link function of the directive, get your myElement after which you want to append your element. Then create your element using angular.element(). Then compile it using $compile service and pass the scope in which you are in now.(Here this scope is the directive scope). After getting the compiled dom element you can just append it after your myElement.
here is an example about how you can add element dynamically from directive:
var elementToBeAdded = angular.element('<input ng-model="phone" id="phone">');
elementToBeAddedCompiled = $compile(elementToBeAdded)(scope);
myElement.append(elementToBeAddedCompiled);
If you add your element using $compile service in your directive, angular will recognize your dynamically added element.
I was trying to accomplish this without using directives, but it seems the best way (and probably the only proper way) of adding multiple elements to DOM in Angular is by defining a custom directive.
I found a very nice example here http://jsfiddle.net/ftfish/KyEr3/
HTML
<section ng-app="myApp" ng-controller="MainCtrl">
<addphone></addphone>
<div id="space-for-phones"></section>
</section>
JavaScript
var myApp = angular.module('myApp', []);
function MainCtrl($scope) {
$scope.count = 0;
}
//Directive that returns an element which adds buttons on click which show an alert on click
myApp.directive("addbuttonsbutton", function(){
return {
restrict: "E",
template: "<button addbuttons>Click to add buttons</button>"
}
});
//Directive for adding buttons on click that show an alert on click
myApp.directive("addbuttons", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){
scope.count++;
angular.element(document.getElementById('space-for-buttons')).append($compile("<div><button class='btn btn-default' data-alert="+scope.count+">Show alert #"+scope.count+"</button></div>")(scope));
});
};
});
//Directive for showing an alert on click
myApp.directive("alert", function(){
return function(scope, element, attrs){
element.bind("click", function(){
console.log(attrs);
alert("This is alert #"+attrs.alert);
});
};
});
in the angular mind you should manipulate the dom from the JS code.
and use ng-* directive
So i don't know you code but i think you just need to do something like :
View
<button ng-click="showAction()">Show the phone input</button>
<input ng-show="showPhone" ng-model="phone" id="phone">
Controller
app.controller('ctrl', [function(){
$scope.showPhone = false;
$scope.showAction = function() {
$scope.showPhone = true;
};
}]);
Outside of Angular you would need to use the event handler to recognize new elements that are added dynamically. I don't see enough of your code to test, but here is an article that talks about this with $.on():
Jquery event handler not working on dynamic content
Here is a good article that will help with directives and may solve your problem by creating your own directive:
http://ruoyusun.com/2013/05/25/things-i-wish-i-were-told-about-angular-js.html#when-you-manipulate-dom-in-controller-write-directives
I am new to angularJS. I am trying to implement the a simple functionality wherein there are a number of checkbox options and one specific function needs to be called on click of these checkbox options something similar to
$(parentDiv).on("click",'input[type="checkbox"]', functionToBeCalled});
I am trying to use a directive for this section with content similar to
<div>
<span ng-repeat="subsection in section"><input type="checkbox" value="subsection.name"></input> {{subsection.name}} </span>
</div>
Directory
app.directive('myDir', function() {
return {
restrict: 'AE',
replace: true,
templateUrl: 'filter.html',
link: function(scope, elem, attrs) {
elem.find("input").bind('click', function() {
//do some data manipulation
});
});
};
});
When i use the link function I find that if I use elem.find('input[type="checkbox"]) gives me an empty object so it is not compiled before that. Kindly let me know how to address this situation. I did a bit of a research and found out about compile function which manipulates DOM before linking,but i am not able to think of the approach ahead. Any help will be appreciated. Thanks in advance.
Use ngClick or ngChange Directive inn angularjs
<div>
<span ng-repeat="subsection in section">
<input type="checkbox" value="subsection.name" ng-click="myFunc(subsection.name)" /> {{filter.name}} </span>
</div>
here in the example i used ng-click = "myFunc" and pased the value in that function
It might help for us to know how you directive looks like and specifically what element that directive is attached to, since you're speaking about elem.
Nevertheless, if you just need functions to be called when the checkbox is clicked, you can use built-in angular functions for this:
ngChange
ngClick
I am building a console like application using AngularJS. One of my requirements is that the div containing the console is scrolled down automatically. I achieved that using a directive that is monitoring the content and adjusting the scrollTop property of the div accordingly:
app.directive('autoScroll', function () {
return {
restrict: 'A',
link: function (scope, element, attrs, ctrls) {
var scrollToBottom = function () {
element[0].scrollTop = element[0].scrollHeight;
};
scope.$watchCollection('outputLines', scrollToBottom);
}
};
});
The first approach to get lines displayed was to use ng-repeat in combination with an unordered list and the curly brackets syntax for binding:
<div auto-scroll id="withHtml" class="list">
<ul>
<li ng-repeat="line in outputLines">{{line.text}}</li>
<li>>> {{currentInput}}</li>
</ul>
</div>
This worked beautifully as you can see in the green output area in this fiddle. The input line (the one that starts with >> is always visible.
But one of the requirements also is to render HTML in the output lines. I thus started using the ngSanitize module and switched from curly brackets syntax to using the ng-bind-html directive:
<div auto-scroll id="withHtml" class="list">
<ul>
<li ng-repeat="line in outputLines" ng-bind-html="line.text"></li>
<li>>> {{currentInput}}</li>
</ul>
</div>
That results in the input line always moving out of the visible area. As you can see in the red output area in the fiddle mentioned above:
The output generated looks the same in both cases. So here's my question: Why does scrolling behave differently depending on whether I use the curly brackets syntax or the ng-bind-html directive?
It's a timing issue.
If you do the following:
var scrollToBottom = function () {
console.log(element[0].scrollHeight);
element[0].scrollTop = element[0].scrollHeight;
};
You will notice that after a few inputs the scrollHeight at the time of setting scrollTop will differ.
I haven't dug deeper into the source code to see what causes this, but you can use $evalAsync to make sure the code is run after the DOM has been maniuplated by Angular, but before the browser renders:
var scrollToBottom = function() {
scope.$evalAsync(function() {
element[0].scrollTop = element[0].scrollHeight;
});
};
Demo: http://jsfiddle.net/cBny9/