AngularJs - Access to DOM element inside ng-repeat - angularjs

I have the next template:
<div ng-repeat="friend in friends | filter:filterFriendsHandler">
{{friend.name}}
</div>
and in my controller i have:
$scope.filterFriendsHandler = function(friend){
//HERE I WANT TO ACCESS TO FRIEND DOM ELEMENT; to do something like this:
//$(friendElement).text('foo');
}
Thanks

I'm going to answer the specific question here, yes I understand this isn't the "angular" way of doing things. If you want to do things the "correct" way, then don't do this, use a directive. There, disclaimers aside, here's how to do it:
Basically, what you want to do is give the DOM element an ID based on the $index or a unique value in your ng-repeat object. Here, I'll just use $index.
<div ng-repeat="friend in friends" id="friend_{{$index}}" ng-bind-html="doSomethingBadToTheDom('friend_' + $index)">
{{friend.title}}
</div>
Then, inside your controller, just query the DOM for the element with that ID:
$scope.doSomethingBadToTheDom = function(ele_id) {
var element = document.getElementById(ele_id);
element.innerHTML = "I'm abusing angular";
}
We're using ng-bind-html here because the DOM element will exist when your controller function executes, in the case of something like ng-init, it won't.
Again, this goes against everything angular stands for, so if you're trying to follow angular best practices, don't do this.
I've run into situations where the technique is useful though, especially when dealing with non-angular libraries, or those times when the "angular way" is more trouble than it's worth.

You need to use a directive for that
<div ng-app="test-app" ng-controller="MyController">
<div ng-repeat="friend in friends" nop>
{{friend.title}}
</div>
</div>
JS
app.directive('nop', function(){
return {
link: function(scope, elm){
console.log('eee', elm, arguments);
elm.css('color', 'red');
}
}
});
demo: Fiddle

Related

Is there a way to rename an Angular variable in the view?

In my Angular project, I am using a particular value that in my controller is called something like:
$scope.feature1.items.thisItem
There's a particular <div> in my view that uses thisItem many times and it's quite messy to be referring to it as {{feature1.items.thisItem}} for example:
<div id="{{feature1.items.thisItem}}header>
<h1> You've reached the section about {{feature1.items.thisItem}} </h1>
</div>
Is there any way to rename this variable in the view? I would like to simply call it one. I've tried {{feature1.items.thisItem as one}} but that didn't work. Any other ideas?
Yes! ng-init was designed for this very purpose - aliasing another variable:
<div ng-init="thisItem = feature1.items.thisItem">
<h1> You've reached the section about {{thisItem}} </h1>
</div>
I would advise against using ng-init, it's not designed for this purpose. From Angular's documentation:
The only appropriate use of ngInit is for aliasing special properties of ngRepeat, as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.
You'll want to create a custom controller for this purpose. Something like the following:
module.controller('RenameController', function($scope) {
$scope.one = null;
$scope.$watch('feature1.items.thisItem', function(value) {
$scope.one = value;
});
});
Yes you can use ng-init on the top of your DOM element
<div ng-app='app'>
<div ng-controller="MyController" ng-init="myVar=feature1.items.thisItem">
{{myVar}}
</div>
</div>

Is it possible to delete all scope variable of a controller? AngularJS

I am new to AngularJS and i dont know is it possible to delete all scope variables of a controller.I am using ng-controller with ng-repeat, like this.
<div ng-controller="main">
<div ng-repeat="x in list" ng-controller="test">
<input type="text" ng-model="text">
<span ng-click="remove($index)"> x </span>
<div>
</div>
JS
myapp.controller('main',function($scope){
$scope.list=[1,2,3,4]
})
myapp.controller('test',function($scope){
$scope.text="untitiled"
})
I want to remove the clicked scope.Can anyone help me or please suggest me a better way. Thanks
The question isn't very clear, but it looks like you may want to remove the item after clicking. Since you are passing into the remove function the index, you can splice it out. The DOM will autoupdate and remove that from the list:
$scope.remove = function(i) {
$scope.list.splice(i,1);
console.log($scope.list);
}
In the event you are doing something different in that you only want to hide it, you would push the index onto another array and then use something like ng-show or ng-hide.
$scope.remove2 = function(i) {
$scope.hideList.push(i);
}
$scope.shouldHide = function(i) {
return $scope.hideList.indexOf(i)!=-1;
}
<div ng-repeat="number in list2" >
{{number}}
<span ng-hide='shouldHide($index)' ng-click="remove2($index)"> x </span>
</div>
Here is a simple example of both scenarios. In real life, usually we are dealing with arrays of objects and what you might be doing is setting a property on one of the objects to hidden and controlling it that way.
Demo: http://plnkr.co/edit/G7UINKUCBJ4yZhQNtuJ2?p=info
If you actually want to remove all the keys from the scope:
function removeKeys() {
for(key in $scope) {
if (key.substr(0,1)!='$' && key!='this')
delete $scope[key];
}
}

AngularJS - using a custom directive to 'emulate' Knockout 'with' binding

The topic title is extremely deceptive, but I could not think of a different way to briefly word this.
Actual Question
(tl;dr)
How can I create a part of the HTML where any expressions are in the immediate context of a given object, like this:
<div [i want to use $scope.user]>
{{ Name }} // this is really $scope.user.Name
{{ Joined }} // this is really $scope.user.Joined
{{ Email }} // this is really $scope.user.Email
</div>
Details
(long winded explanation of why I am lost)
I know that I can do this using a Controller, but from what I am grasping, that isn't what I should do. The tutorials I read all seem to accomplish this, but I'm still left pretty confused.
I know this is doable - and in fact quite normal, in angular. But essentially my goal is to take code like this ...
<div ng-repeat="item in Items">
{{ item.Name }}
{{ item.Price }}
</div>
And use it on a slightly more personal level on non-repeated code. I believe this would be a custom directive? I've been reading a lot of resources, and they're very useful, but I'm having a difficult time associating the relationship between the DOM and the options of the directive. I'll try hard to explain...
Assume I have a model like this;
{
Name: "Ciel",
Joined: "2014/7/30",
email: "emailaddress#domain.com"
}
My understanding is that I would actually wire this in the $scope part of my controller, so .. I think it would go like this?
(function(){
var app = angular.module("program", []);
app.controller("UserController", function($scope){
$scope.user = {
Name: "Ciel",
Joined: "2014/7/30",
Email: "emailaddress#domain.com"
};
});
})();
Now, if I am grasping it right, I should be able to create a directive? to actually use this fluently in my HTML, kind of like this ...
<div ng-model="user">
{{ Name }}
{{ Joined }}
{{ Email }}
</div>
But when I tried this out, I am getting a bit confused. Because this doesn't actually seem to do what I thought. I guess what I am really lost on is how we're using ng-model here. I thought it was more like knockout's with: user, so it would be more like this;
<div data-bind="with: user">
{{ Name }}
{{ Joined }}
{{ Email }}
</div>
But that isn't seeming to be the case. When I tried running this on my own, I seem to be required to do it like this..
<div>
{{ user.Name }}
{{ user.Joined }}
{{ user.Email }}
</div>
And ng-model doesn't even seem needed.
A directive! See fiddle: http://jsfiddle.net/8drZr/1/
app.directive('with', ['$parse', function($parse) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
var withContext = $parse(attrs['with'])(scope.$parent);
angular.extend(scope, withContext);
}
};
}]);
The trick is setting scope: true. It will create a new child scope so as not to pollute the parent and the directive places everything under the given "with" object in this new scope using angular.extend() (for those familiar with jQuery, same as $.extend()).
Now there is a limitation, as pointed out in the comments. Two way binding between top-level properties (*) will not occur. This is a known limitation of Angular and the prototypical inheritance of scopes.
(*) By top-level properties I mean bindings like <input ng-model="value" />; non-top-level would be <input ng-model="bar.value" />
A more complicated implementation can surmount this problem, at the cost of more watches. A naive first implementation can be seen here: http://jsfiddle.net/mxG5r/1/
It is naive because for( x in withContext ) will iterate over properties that may not be desirable to watch, e.g. functions/methods.

ng-repeat with ng-include not working

I am trying to use an ng-repeat that includes an ng-include. The problem is that the first element in the ng-repeat is just the ng-include template with none of the data from the ng-repeat filled in. Is there a way I can somehow bind the template from the ng-include so it works on the first ng-repeat?
<div ng-repeat="item in items">
<div ng-include src="'views/template.html'"></div>
</div>
For example, if my ng-repeat contains 10 items, then the first item that is rendered will just be the empty template. Items 2-10 WILL be rendered as they should be. What am I doing wrong?
First make sure that the data that is contained in the first index of items actually has the data that you want.
One possible solution to your problem would be to simply not show the first index of the ng-repeat:
<div ng-repeat="item in items" ng-show="!$first">
<div ng-include src="'views/template.html'"></div>
</div>
This may not actually tackle the root of your problem, but it may still get your application working a bit more like what you expect.
Another possible solution:
<div ng-repeat="item in items" ng-include="'views/template.html'"></div>
see example here:
http://plnkr.co/edit/Yvd73HiFS8dXvpvpEeFu?p=preview
One more possible fix just for good measure:
Use a component:
html:
<div ng-repeat="item in items">
<my-include></my-include>
</div>
js:
angular.module("app").directive("myInclude", function() {
return {
restrict: "E",
templateUrl: "/views/template.html"
}
})
I ran into the same problem, and finally figured out that the first element has not been fetched and compiled in time for the first ng-repeat iteration. Using $templateCache will fix the problem.
You can cache your template in a script tag:
<script type="text/ng-template" id="templateId.html">
<p>This is the content of the template</p>
</script>
Or in your app's run function:
angular.module("app").run(function($http, $templateCache) {
$http.get("/views/template.html", { cache: $templateCache });
});
You can also use $templateCache inside your directive, although it's a bit harder to setup. If your templates are dynamic, I would recommend creating a template cache service. This SO question has some good examples of template caching inside a directive and a service:
Using $http and $templateCache from within a directive doesn't return results
Using a directive worked for me: https://stackoverflow.com/a/24673257/188926
In your case:
1) define a directive:
angular.module('myApp')
.directive('mytemplate', function() {
return {
templateUrl: 'views/template.html'
};
});
2) use your new directive:
<mytemplate />
... or if you're concerned about HTML validation:
<div mytemplate></div>

Duplicate attributes when using directive compile with transclude

JsFiddle of the issue: http://jsfiddle.net/UYf7U/
When using the angularJs transclude within a directives compile, it will duplicate any
attribute properties. I.e.
<a class="myClass">my link</a>
Will become
<a class="myClass myClass">my link</a>
Similarly, when using an ngClick
<a ng-click="myFunction()"> my link</a>
Will become
<a ng-click="myFunction() myFunction()"> my link</a>
The fiddle demonstrates this, and unfortunately it crashes. It's a stripped down version of what I'm trying to implement.
Is there a way around this? I've posted the issue to github to: https://github.com/angular/angular.js/issues/2576
When clicking on Hello the word "clicked" should appear in an alert.
This happens because myDirective is being initialized twice - first as part of your markup:
<div class="transcludeMe">
<div data-transclude-this="here">
<div class="myDirective"></div>
</div>
</div>
Second in the transcludeMe directive - since you do this in the compile stage of the directive initialization:
transcludeHere[0].innerHTML = clone[x].innerHTML
Since you use replace:true all attributes of the original element will get copied to the template element. If you remove this your example works, but you still be aware that myDirective is getting initialized two times: http://jsfiddle.net/tkzgG/
How important is it to you to specify the directive name as a class? This issue does not occur when the directives are used as elements directly.
See http://jsfiddle.net/smmccrohan/cfP3U/
Like thus, plus replacing the restrict: 'C' with restrict: 'E' in the directive definitions (and making some case changes to avoid an issue there):
<div ng-app="app">
<div ng-controller="ParentCtrl">
<transcludeme>
<div data-transclude-this="here">
<mydirective />
</div>
</transcludeme>
</div>
</div>
I found a different way to do multi-transclusion and that fixed my problem entirely, here's the updated fiddle for my problem being fixed: http://jsfiddle.net/UYf7U/1/
The code came from my previous question here: Multiple transclusions of separate html in an update that I did not see.
The fiddle will be out of date, but this is my final multi transclusion function. I've mode the logic into compile instead of the controller so that it can transclude dom that needs to have things like ng-repeat
.directive('multiTranscludeTo', function($rootScope){
return {
compile: function(tElement, tAttributes, transclude){
var baseScope = this;
transclude($rootScope, function(clone){
for (var x = 0; x < clone.length; x++){
var child = angular.element(clone[x]);
var viewName = child.attr('data-multi-transclude-from') || child.attr('multi-transclude-from');
if (viewName && viewName.split(" ")[0] == tAttributes["multiTranscludeTo"]){
tElement.html(clone[x].innerHTML);
}
}
});
}
}
})

Resources