Accessing yet-to-be-compiled directive's DOM elements from controller - javascript

I've got a controller that executes before Angular compiles a directive. The controller needs to manipulate a DOM element that only exists after the directive has been compiled. How can I select that DOM element, to manipulate it, if it's not in the DOM yet?
A contrived example to illustrate this follows. Let's say I have the following markup:
<div ng-controller="CustomerCtrl">
<div customer-list></div>
</div>
customerList is a directive that is transformed into:
<select class="customer-list">
<option value="JohnDoe">John Doe</option>
<option value="JaneDoe">Jane Doe</option>
</select>
CustomerCtrl looks like:
.controller('CustomerCtrl', function() {
var customerList = angular.element('[customer-list]'),
firstCustomer = customerList.children('option:first');
// firstCustomer is length === 0 at this point, so
// this code does not work
firstCustomer.prop('selected', 'selected');
});
My solution was to do this:
.controller('CustomerCtrl', function($timeout) {
var customerList = angular.element('[customer-list]');
// this works
$timeout(function() {
customerList.children('option:first').prop('selected', 'selected');
});
});
What I'm wondering is, is this the appropriate way to do this--using a $timeout()? Is there a best practice about this?

You should be using the directive's link function to do this sort of manipulation. Then you won't have ot use timeout. Such DOM manipulation really belongs to the directive rather than the controller. The controller is simply the holder of scope and really shouldn't have much much of application/ui logic in it.
The link is guaranteed to be called after your directive is compiled so you should be good if you use link.

Related

Angular ng-model in appended Html code not working

This is my code. Where I dynamically add html on a button click.but the ng-model which I gave there is not working.
Can anyone solve my issue
$scope.addParam = function(selected) {
if (selected == "bucket_policy_privacy_status") {
var myElement = angular.element(document.querySelector('#inputParameters'));
myElement.append('<input type="text" style="width:220px" class="form-control" name="" disabled="" value=' + selected + '> <select class="form-control" style="width:196px" ng-model="fields.privacyStatus" ><option value="range">Range</option><option value="public">Public</option><option value="private">Private</option></select> <br><br>');
$scope.$watch('fields.privacyStatus', function() {
var privacy_status = $scope.fields.privacyStatus;
alert();
var status = {
"term": {}
}
status.term[selected] = privacy_status;
output.push(status);
});
$('#params option[value="bucket_policy_privacy_status"]').remove();
$scope.input.select = "bucket_owner";
}
};
There are a few things you will need to change:
You need to use Angulars $compile option to evaluate angular attributes/expressions when inserting HTML dynamically.
Adding the line below after you added the element ot the DOM should do the trick:
$compile(angular.element('.form-control'))($scope);
It basically just let's angular know there is something new it should evaluate and start watching.
(Don't forget to add $compile to your module dependencies).
Further to that I assume you have actually added an object called fields on your $scope?
Another thing you could do is use ng-change on your form instead of using $watch. The function you bind on ng-change would be invoked every time one of the selects in your form changes.
For further reading have a look at this:
https://docs.angularjs.org/api/ng/service/$compile
I think that will not work, you are adding only dom, that's all, there's no bind.
One way is to create directive which will do that, or add that input in template with "ng-hidden", and on click just show.

Create DOM elements and set a click event in AngularJS directive

how can I create in an AngularJS directive some DOM elements and set on them a click event? In my directive I create my elements in this way:
var list = document.createElement("div");
list.classList.add('myList');
for(var i = 0; i < n; i++) {
var item = document.createElement("div");
item.classList.add('myItem');
list.appendChild(item);
}
so I have an external div container that contains some div elements.
This is my generated HTML:
<div class="myList">
<div class="myItem">
<div class="myItem"></div>
<div class="myItem"></div>
<div class="myItem">
</div>
In the same directive I have to set a click event on those elements, in jQuery I can do:
$(".myItem" ).on( "click", function() {
// Do something
});
I try to that in Angular in many ways but I have problems to set the on click event:
var list = document.querySelector('.myList');
_.forEach(list.children, function(value, index){
var item = document.querySelector(value);
item.bind("click",function(){
// Do something
});
});
I get an error:
Failed to execute 'querySelector' on 'Document': '[object HTMLDivElement]' is not a valid selector.
Also, if I want get all myItem directly (without list.children) I write use:
var item = document.querySelector('.myItem');
I get:
item.bind is not a function (caused by "undefined")
I can set an ng-click in the directive... how?
item.on( "click", function() {
// Do something
});
If I use .on() method it's undefined like .bind().
Anyone can help me? Thanks in advice :)
I believe what you need is
<div class="myList">
<div class="myItem" ng-click="yourClickFn()">
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()"></div>
<div class="myItem" ng-click="yourClickFn()">
Then in your Angular controller:
$scope.yourClickFn = function(){
//the code you want to execute here
}
Do you have a real reason to create your elements this way? It's really not the angular-way of doing things. You should use an ng-repeat on to create your html.
Also if you have a chance to update to anguar 1.5+ you could use components instead of directives, this would make your life easier.
Update
Alternatively you could do it with jQuery after the elements are created actually. I think it's easier to read than the plain js version.
Put a class on them and do something like:
$('.myClass').on('click', function() {
//your code
if (!$scope.$$phase) { // this checks whether you are already in a digest cycle or not - you probably won't be at this phase.
$scope.$apply(); // this will update the html if you did something to the model above this if
}
});
If you have a directive put this code to the link function, if you have a component then put it into the $postLink lifecycle hook (works after 1.5.3 I think) as these functions are called after your html has been already generated.
Usually these are the places for "messy" or "non-angular" code ^_^

Directive communication - sharing a reference to an inner HTML element

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.

Adding an element with directives in Angular

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

get the text of div using angularjs

i want to get the text of div using angularjs . I have this code
<div ng-click="update()" id="myform.value">Here </div>
where as my controller is something like this
var myapp= angular.module("myapp",[]);
myapp.controller("HelloController",function($scope,$http){
$scope.myform ={};
function update()
{
// If i have a textbox i can get its value from below alert
alert($scope.myform.value);
}
});
Can anyone also recommand me any good link for angularjs . I dont find angularjs reference as a learning source .
You should send the click event in the function, your html code should be :
<div ng-click="update($event)" id="myform.value">Here </div>
And your update function should have the event parameter which you'll get the div element from and then get the text from the element like this :
function update(event)
{
alert(event.target.innerHTML);
}
i just thought i put together a proper answer for everybody looking into this question later.
Whenever you do have the desire to change dom elements in angular you need to make a step back and think once more what exactly you want to achieve. Chances are you are doing something wring (unless you are in a link function there you should handle exactly that).
So where is the value comming, it should not come from the dom itself, it should be within your controller and brought into the dom with a ng-bind or {{ }} expression like:
<div>{{ likeText }}</div>
In the controller now you can change the text as needed by doing:
$scope.likeText = 'Like';
$scope.update = function() {
$scope.likeText = 'dislike';
}
For angular tutorials there is a good resource here -> http://angular.codeschool.com/
Redefine your function as
$scope.update = function() {
alert($scope.myform.value);
}
A better way to do it would be to use ng-model
https://docs.angularjs.org/api/ng/directive/ngModel
Check the example, these docs can be a bit wordy

Categories