Getting DOM element in AngularJS directive - javascript

I´m trying to get a DOM element in order to change it inside a AngularJS1.0.6 directive.
HTML:
<li ng-repeat="car in cars" data-highlight="{{car.id}}">
Directive:
var iw = angular.element(document.querySelector('#iw-' + id));
console.log("iw=" + iw);
Please see the plunker for details: https://plnkr.co/edit/dP2cvut4f5ao5pFe6ka0?p=preview

After little research, I was able to make it work. Basically first issue comes when you use directive inside ng-repeat (also directive).
In this case directive's scope must be set for example like: scope: {car: '=highlight'}. This can desribe what happens. I would rather use <ul highlight="cars"></ul> along with directive's template like '<ul><li ng-repeat="car in cars"></li></ul>'. So ng-repeat would be inside directive.
When you get through, another issue comes in form of syncing two directives (different scopes). You need DOM ready to select element from another directive. I saved your compliled element to car object - it can be count as workaround that make it easier.
Forked plunker here

<li ng-repeat="car in cars track by $index" data-highlight="{{car.$index}}">
or
if whatever property exists, in your case, car must have id property

Related

Angular ng-model not binding to textarea [duplicate]

Here is my plnkr: http://plnkr.co/edit/n8cRXwIpHJw3jUpL8PX5?p=preview You have to click on a li element and the form will appear. Enter a random string and hit 'add notice'. Instead of the textarea text you will get undefined.
Markup:
<ul>
<li ng-repeat="ticket in tickets" ng-click="select(ticket)">
{{ ticket.text }}
</li>
</ul>
<div ui-if="selectedTicket != null">
<form ng-submit="createNotice(selectedTicket)">
<textarea ng-model="noticeText"></textarea>
<button type="submit">add notice</button>
</form>
</div>
JS part:
$scope.createNotice = function(ticket){
alert($scope.noticeText);
}
returns 'undefined'. I noticed that this does not work when using ui-if of angular-ui. Any ideas why this does not work? How to fix it?
Your problem lies in the ui-if part. Angular-ui creates a new scope for anything within that directive so in order to access the parent scope, you must do something like this:
<textarea ng-model="$parent.noticeText"></textarea>
Instead of
<textarea ng-model="noticeText"></textarea>
This issue happened to me while not using the ng-if directive on elements surrounding the textarea element. While the solution of Mathew is correct, the reason seems to be another. Searching for that issue points to this post, so I decided to share this.
If you look at the AngularJS documentation here https://docs.angularjs.org/api/ng/directive/textarea , you can see that Angular adds its own directive called <textarea> that "overrides" the default HTML textarea element. This is the new scope that causes the whole mess.
If you have a variable like
$scope.myText = 'Dummy text';
in your controller and bind that to the textarea element like this
<textarea ng-model="myText"></textarea>
AngularJS will look for that variable in the scope of the directive. It is not there and thus he walks down to $parent. The variable is present there and the text is inserted into the textarea. When changing the text in the textarea, Angular does NOT change the parent's variable. Instead it creates a new variable in the directive's scope and thus the original variable is not updated. If you bind the textarea to the parent's variable, as suggested by Mathew, Angular will always bind to the correct variable and the issue is gone.
<textarea ng-model="$parent.myText"></textarea>
Hope this will clear things up for other people coming to this question and and think "WTF, I am not using ng-if or any other directive in my case!" like I did when I first landed here ;)
Update: Use controller-as syntax
Wanted to add this long before but didn't find time to do it. This is the modern style of building controllers and should be used instead of the $parent stuff above. Read on to find out how and why.
Since AngularJS 1.2 there is the ability to reference the controller object directly instead of using the $scope object. This may be achieved by using this syntax in HTML markup:
<div ng-controller="MyController as myc"> [...] </div>
Popular routing modules (i.e. UI Router) provide similar properties for their states. For UI Router you use the following in your state definition:
[...]
controller: "MyController",
controllerAs: "myc",
[...]
This helps us to circumvent the problem with nested or incorrectly addressed scopes. The above example would be constructed this way. First the JavaScript part. Straight forward, you simple do not use the $scope reference to set your text, just use this to attach the property directly to the controller object.
angular.module('myApp').controller('MyController', function () {
this.myText = 'Dummy text';
});
The markup for the textarea with controller-as syntax would look like this:
<textarea ng-model="myc.myText"></textarea>
This is the most efficient way to do things like this today, because it solves the problem with nested scopes making us count how many layers deep we are at a certain point. Using multiple nested directives inside elements with an ng-controller directive could have lead to something like this when using the old way of referencing scopes. And no one really wants to do that all day!
<textarea ng-model="$parent.$parent.$parent.$parent.myText"></textarea>
Bind the textarea to a scope variable's property rather than directly to a scope variable:
controller:
$scope.notice = {text: ""}
template:
<textarea ng-model="notice.text"></textarea>
It is, indeed, ui-if that creates the problem. Angular if directives destroy and recreate portions of the dom tree based on the expression. This is was creates the new scope and not the textarea directive as marandus suggested.
Here's a post on the differences between ngIf and ngShow that describes this well—what is the difference between ng-if and ng-show/ng-hide.

using ng-repeat with directives causes child directives not to update

so if found this very interesting bug in angular.js. if you have a custom directive inside a ng-repeat that is actively changing the variables in the directive don't update. meaning if i have 3 elements in my array for ng-repeat it initializes just fine but if i delete element 1 from the array any variables that element 1 had passed to its child directive somehow end up in element 2's child directive here is my example code.
<div ng-app='testing'>
<div ng-controller='testing as test'>
<div ng-repeat='item in test.example track by $index'>
{{item.title}}
<child scope='item.data'></child>
<button ng-click="test.delete($index)">
Delete
</button>
</div>
</div>
</div>
then in my js file
console.log('hello world');
var app=angular.module('testing',['testingChild']);
app.controller('testing',[function(){
this.example=[{
title:"this is the first title",
data:"this is the first index"
},{
title:"this is the second title",
data:"this is the second index"
},{
title:"this is the third title",
data:"this is the third index"
}];
this.delete=function(index){
this.example.splice(index,1);
};
}]);
var child=angular.module('testingChild',[]);
child.directive('child',[function(){
return{
restrict:"E",
scope:{
parent:"=scope"
},
template:"<div>{{child.parent}}</div>",
controller:['$scope',function($scope){
this.parent=$scope.parent;
}],
controllerAs:"child"
};
}]);
and i have a functioning jsfiddle here. all you have to do to see it work is delete one of the first elements. does anyone know what causes this and how to fix it?
Side note:
I thought it might be useful also to mention that when using this in a slighty different situation with editable elements in the child (like a text box) the data binding worked from the child to the parent. so assigning a variable attached to the controller to the scoped variable from the parent worked in that direction. this seems to be the only situation i have come across where it would be from the parent to the child and that is what is not working.
Change:
template:"<div>{{child.parent}}</div>",
controller:['$scope',function($scope){ this.parent=$scope.parent; }]
To:
template:"<div>{{parent}}</div>"
controller:function(){ }
since you are using controllerAs syntax, you dont need the $scope injection.
For the binding work as expected, you dont use child.parent, only parent (or whatever you inject in the this context on your controller
I found a property in the $compile service that fixes this problem. adding the attribute bindToController:true to the directive takes all of the variables defined in your scope attribute and attaches them to the controller rather then the scope itself meaning the 2 way data binding is to the variable on the controller rather then the variable on the scope. so the end result has these changes
in your directive definition
scope:{
parent:"=scope"
},
bindToController:true,
and in the controller remove the this.parent=$scope.parent
here is an updated jsfiddle

AngularJS directive wait until templates rendered

I'm trying to wrap the SharePoint People Picker in an AngularJS directive. In order to initialise a people picker I need to place a div on the page, give it an ID and pass that ID into a SharePoint function.
I have this working with a basic directive like this:
<sp-people-picker id="test"></sp-people-picker>
But I wish for the directive to be useable anywhere, including in a repeating section:
<div ng-repeat="item in dataset">
<sp-people-picker id="test-{{ $index }}"></sp-people-picker>
</div>
This fails. I stepped through the code to see what was going wrong and found that while I was happily calling the SharePoint people picker function with "test-0" it was failing to find the element. document.getElementById("test-0") returned null. The reason for this is that my div still had the id "test-{{ $index }}" and only gets "test-0" AFTER my directive has compiled.
How can I make sure my directive runs after the {{ }} has been rendered?
(Not tagging with SharePoint as the SharePoint stuff is just the context, it's not actually relevant to the issue I'm trying to solve)
You need to use attrs.$observe inside your directive link function, that will act as the same as like $watch, the difference is it can watch on the {{}} interpolation directive, Your link function will look like below. It call function whenever interpolation directive gets evaluated.
Directive(Link Function)
link: function(scope, element, attrs){
attrs.$observe(attrs.id, function(newVal, oldVal){
//here you can get new value & `{{}}` is evaluated.
});
}

AngularJS, binding to a model in ng-repeat in a directive

So I am trying to bind inside a directive (to access outside) to a model inside of an ng-repeat. So in the outer controller I have a variable I would like to bind in like
//in the directive scope
filterArray: '='
Inside the directive that would be bound inside the directive to a model in an ng-repeat like so -
//inside the directive
<li ng-repeat="value in filter.values">
<input type="checkbox" ng-model="filterObject[filter.name][value]" ng-change="filterChange()">{{value}}
</li>
This worked fine until I changed the directive to have an isolate scope, now it is saying cannot set property of undefined. Is there any way to get this working as intended? The idea is the variable would build out when the user clicks the inputs so the outer controller would be able to see the built object.
Apologies if this is a bit confusing - I have made a fiddle to clarify : https://jsfiddle.net/vt1uasw7/42/ .
I want the outer controller to have access to the object built by binding the model - again this was working before I added the isolate scope. Thanks!
Edit: maybe the trick in this case is not to use the isolate scope? This one has me stumped, I've tried every combination of scope attributes :(.
If you can't pre-initialize your outter filterObject you can let the directive controller handle that for you:
$scope.filterArray[$scope.filter.name] = {};
And check your parameters as Claies stated, inside your directive you need to use filterArray and also the attribute name in the outter ng-repeat needs to be "filter-array and not "filterArray".
<div ng-repeat="filter in searchResults.filters" class="my-directive" filter="filter" filter-change="filterChange" filter-array="filterObject"> </div>
See this https://jsfiddle.net/vt1uasw7/164/

Template directive calling another directive

I am developing a multi data app that is using angular.js and d3.js. I am having a hard time to include the <svg> into my scope.
What is happening now is that the directive ngTests is being loaded before the ngRepeat executes it's methods.
I am putting the fiddle here so you guys can have a better idea.
jsfiddle
<ng-chart></ng-chart>
PS: I can get the td id on my ngTests directive, but it doesn't update at all <td ng-tests id="histogram{{$index}}".
If I change this line to <td ng-tests id="histogram">, use histogram as ID on my directive and change my ngTests directive to read only "#histogram" it creates my svg 6 times on the first table of my ngRepeat, which is not the result I am expecting.
Thank you.
Do two things. In your ngChart directive you have the index attribute specified like index="index" but it needs to be index="{{$index}}". Then, to read the attribute value from the other directive, instead of accessing the attribute directly like attrs.index you should be using attrs.$observe more like the following:
attrs.$observe('index', function(observedIndex) {
console.log('observedIndex:', observedIndex);
});
This way when the index is changed (e.g. by another directive) you get notified and can update the element's text. I forked the fiddle and got it working here.

Categories

Resources