Got a little situation here.
I'm just learning Angular and I'm stuck with the following problem.
I've a list that repeats itself (ng-repeat) and when it is rendered, you can click on it and I add a class to the list-item you clicked on, jQuery-style:
$('#'+id).addClass("myClass");
And the id is stored in a scope-variable.
But when I change the scope for the list-item, new items are loaded, and when I change them back to the state were I started, I'm trying to add the class to the same id, but doens't work.
Do I miss something?
Thanks in advance
A better approach here would be to alter your data on click and then to conditionally attach a class based on the data:
<div ng-repeat="item in items" ng-class="{myClass:item.changed}">
{{item.name}}
<button ng-click="item.changed = !item.changed">Change me!</button>
</div>
Here it is in action: http://jsfiddle.net/wilsonjonash/NTpLS/
As I mentioned in the comments, when programming in angular, think in terms of your model first. Most things you would achieve in jQuery-land with DOM manipulations can be achieved in angular with model-dependent markup (directives).
Best of luck!
You can use the ng-class instead of addClass : )
ngClass Document
Related
I am still new to Angular and I'm struggling to get the DOM Element of an Angular Click Listener.
What I have is the following component HTML:
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top" (click)="itemClicked($event, !!menuItem.submenu)">
<!-- Additional divs inside... -->
</div>
</div>
I would like to toggle a class of "menuItem__top" when it is clicked. My approach was to use a click event listener but I can't get the source Element to apply the class on.
itemClicked(event, hasSubmenu){
console.log(this) //is the component
let target = event.target || event.srcElement || event.currentTarget;
if(hasSubmenu){
console.log(target);
}
}
It could be done by getting the target of the $event but this way I would need to check the target and move with closest(".menuItem__top") up to the correct source element.
Is there an easy way in Angular to pass the source element of the click listener to the click function or a way to get it inside the click function?
In vanilla JS it would be as easy as using "this" inside the click function, but in Angular this is bind to the component. (In this case, it would be ok to loose the binding to the component if this is the only way.)
I thought about two ways:
Assigning a dynamic reference containing some string and the itemId, passing the itemId and retrieving the reference object based on the itemId in the listener.
Using a #HostListener to listen on every "menuItem__top" click and toggling the class every time.
What do you think is the best way? I feel like I am missing something simple here.
Go the other way around. People are used to jQuery and the way it works (selecting elements already present in the DOM, then adding them a class). So in Angular, they try to do the same thing and grab the element that was clicked, to apply it a class. Angular (and React and others) work the other way around. They construct the DOM from data. If you want to modify an element, start with modifying the data it was generated from.
This <div class="menuItem__top"> that you click on is constructed from let menuItem. All you have to do is add a property, say "isClicked" to menuItem.
<div *ngFor="let menuItem of menu; index as itemId" class="menuItem">
<div class="menuItem__top"
[class.isClicked]="menuItem.isClicked"
(click)="menuItem.isClicked = true">
<!-- Additional divs inside... -->
</div>
</div>
I have an element, let's say a circle.
And there is a list of item in a list view.
Initially, the circle is not rendered. I have set its CSS display property to none, and instead select an item message is displayed. The idea is when the user selects an item from the list, I want to show the circle changing its background-color property associated with selected item.
My approach is something like this
itemSelected(item) {
const itemtColor = item.color;
$('#selected-item-color').css('background-color', itemColor);
}
The issue is:
As the element is not present in the DOM when I try $('#selected-item-color'), it will return an empty list, and the change of property does not do anything. But on next subsequent selections, it works fine.
So, is there any clean way to do this before the element is actually rendered in the DOM either jQuery or JavaScript. Or, should I just look for a way to do this after the element is rendered, which I'm not sure if there is a way with my problem.
Any help is appreciated, thanks!
Edit:
I have the HTML code written in handlebars
The code calling above function
<div class="item-list-content">
{{#each itemList as |item|}}
<div class="list-item" {{action "itemSelected" item}}>{{item.name}}</div>
{{/each}}
</div>
The dynamic content
<div class="item-details">
{{#if selecteditem}}
<div id="selected-item-color"></div>
{{else}}
<div class="item-details-message">Please select an item</div>
{{/if}}
</div>
To solve your problem you can just use ngShow instead of ngIf. There are better ways to do that as well, but for your issue ngShow will solve your problem.
I tried out few things and could not get anything to solve this.
So, I ended up doing the next best thing:
Putting the code to change project color inside a timeout function.
A timeout of 1ms worked for me, however I have not tested it well, and to be on a safer side, an interval of 10ms should work without problems.
setTimeout()
I want to use an ng-if inside an ng-repeat (as described in this question):
However, I want to display a row in a table if and only if the if condition holds. In the answers to the question above, you will get empty div tags where the if condition does not hold. This does not cause much of a problem with divs, but when doing the same with tables, you do not want to have div (or span) tags inbetween tr tags.
Is there another tag or directive that I could use?
A better idea is to use a (custom) filter.
That way, only the needed rows are generated, in stead of hiding/removing existing ones.
I just made a plunk for another question, that showed how to use a filter:
the relevant line is this one:
<tr ng-repeat='item in appVm.users |filter:test'>
you can use a object directly in your code like this:
<tr ng-repeat='item in appVm.users |filter:{age:32}'>
If you put that line in the sample I linked in, you can see how that would work out!
Does this help you?
If you just want to hide these elements, you can consider using ng-show instead of ng-if.
Maybe you can also add the ng-show in the ng-if tag to hide it when empty.
Now, if you do not want to render the content at all, I think the only solution is to remove these elements from your collection in your controller
you can use ng-show.its works for show and hide tags.if you don't want to angular compile inside the tag you can use ng-if.
I have many repeated content elements in a single view. Within each content element, there's an anchor. When a user mouses over this anchor, I want to toggle a class on a sibling element within that particular content element.
Here's a simple example of what I want to do:
<div class="content-element">
<div ng-class="visibleClass">
I should have class 'visible' when the user mouses over the link within content-element div.
</div>
<a ng-mouseover="" ng-mouseleave="" href="#">Mouseover</a>
</div>
I initially wrote a controller to handle this, but the controller's $scope is tied to the entire view, not a single content-element, so this turned out to not be a graceful solution.
There are many 'content-elements' that are not generated with angular, but are just repeated in the template.
I'm fairly new to angular and trying to wrap my head around this new way of thinking. I can definitely solve this problem easy writing some javascript (capture the event, get the target, get the sibling, etc.) but this doesn't seem like the proper way to do it with angular.
So... what's the appropriate angular way to do this? Should I be writing a custom directive?
Simply create a directive with a new scope and have something like this in the HTML:
<div class="content-item">
<div class="" ng-class="{someClass:hovered}">My transparency should change.</div>
<a ng-mouseover="hovered = true">Mouseover me.</a>
</div>
PLUNKER
Note that if you use ngRepeat, it creates isolate scopes automatically and you don't need the directive.
Directive which founds siblings of the element on mouseover event. You can do what you want with the siblings then:
app.directive('mousiee',function(){
return{
restrict: 'A',
link: function(scope,elem,attrs){
var siblings;
elem.on('mouseover',function(){
siblings = $(elem.parent()).siblings();
console.log(siblings);
});
}
};
});
http://plnkr.co/edit/gWkNpiHMUEUBwuug9C3q?p=preview
(Note that I've added jQuery to your index.html)
It seems that getting an element in AngularJS is a bad idea, i.e. doing something like:
$('.myElement')
in say, a controller is not an angular way of doing things.
Now my question is, how should I get something in angular?
Right now, what I'm doing (and is an accepted way of doing it) is by watching a variable, and my directive does something based on it.
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.id == varToWatch)
{
//Run my Directive specific code
}
});
However, while this particular design works for most cases, watch is an expensive operation, and having lots of directives watching can really slow down your application.
TL:DR - What is an angular way of getting a directive based on a variable on the directive? (like the one above)?
If you want to get/set values you don't need to fetch the element using jQuery. Angular data binding is the way to do it.
directives is the way to go if you want to do animations or any kind of element attributes and DOM manipulation.
Your code is basically right; the directive should watch something in the $scope and perform it's logic when that thing changes. Yes, watch statements are expensive, and that is a problem once your number of watches start to approach ~2000.
Looking at your code though, I see one problem:
The variable $scope.varToWatch references an id in the template.
When this variable changes, you want something to happen to the element which has this id.
The problem here is in the first point: The controller should know nothing about the DOM, including the id of any element. You should find another way to handle this, for example:
<div my-directive="one"> ... </div>
<div my-directive="two"> ... </div>
<div my-directive="three"> ... </div>
...etc
And in your directive:
scope.$watch('varToWatch', function (varToWatch) {
if(attrs.myDirective == varToWatch)
{
// Run my Directive specific code
}
});
You are very vague as to what you're trying to achieve, but I'll try to answer in context of your last comment.
I have a lot of the same directives (therefore the code will run on all of them), but I need to get only one directive from the lot.
You talk a lot about getting the right element. The directive element is passed to the link function in the directive. If you are not using this element (or children of it) directly, but rather trying to search for the element you want somehow, you are most likely approaching the problem the wrong way.
There are several ways to solve this, I'm sure. If you're thinking about animations, there is already support for that in Angular, so please don't try reinvent the wheel yourself. For other logic, here are two suggestions:
Secondary directive
If the logic you want to apply to this directive is generic, i.e. it could be applied to other directives in your application, you could create a new directive which works together with directives. You can set prioritization in directive in order to control which directive is executed first.
<main-directive ... helper-directive="{{condition_for_applying_logic}}"></main-directive>
jsFiddle example
Expanding main directive
If the logic is tightly coupled to this directive, you can just create a new attribute, either dynamic or static, and bind to it in the directive. Instead of checking 'attrs.id == varToWatch', you check if $scope.apply-logic === 'true' and apply the logic then.
<main-directive ...></main-directive> <!-- Not applied here -->
<main-directive apply-logic="true" ...></main-directive> <!-- Applied here -->
<main-directive apply-logic="{{some.varOnScope}}"...></main-directive> <!-- Conditional -->
Please comment if something is unclear.