In Angular, Communicating a matching $index between two different directives - javascript

I have two directive:
<wa-svg ng-repeat="page in Pages" ng-mouseover="showTooltip($index)>
<wa-tooltip ng-repeat="page in Pages" ng-class="{on: matchedIndex}">
Question:
On mouseover of a specific wa-svg how do I match the $index of wa-svg to make the matchedIndex of wa-tooltip return true. Thus, the class of ongets appended to wa-tooltip
I believe it will take an isolate scope or maybe possible through ng-model but it's confusing.
Note: I cannot nest these elements because wa-svg is a svg object and you cant nest very well unless using foreignObject and blah blah blah.

Hmm how about a shared controller then?
A wrapper controller which has this function:
$scope.showTooltip= function($index) {
$scope.hoveredIndex = $index;
}
And in your tooltip directive do:
<wa-tooltip ng-repeat="page in pages" ng-class="{on: $index === hoveredIndex}">
Will that work for you?

This should work:
<div ng-init="hovered = {}">
<wa-svg ng-repeat="page in Pages" ng-mouseover="hovered.index = $index" />
<wa-tooltip ng-repeat="page in Pages" ng-class="{on: hovered.index == $index}" />
</div>
To sum up:
Hovering over the wa-svg sets the hovered.index, which you can access in wa-tooltip.
The only reason for the hovered = {} part is because the repeaters create new scopes, so I've created an object outside of the new scope, so that the index is accessible from both repeaters. You could / should put that statement into a controller.

Related

How can I isolate/encapsulate $index in Angular using 2 ng-repeat directives on the same page?

I have a website built with Angular 1.4 with 2 slideshows. One in the header and one for a partner logo slider. They are in the same view when all it said and done.
Header
<div layout="row">
<div flex="100" flex-gt-sm="70" hide-xs class="header-slider center">
<img
ng-repeat="i in slides"
class="header-slide-animation"
src="{{i.img}}"
ng-hide="!isCurrentSlideIndex($index)"
ng-class="{'active':isCurrentSlideIndex($index)}"/>
</div>
And then here's the logo slider
<div class="partners-slider center">
<a ng-repeat="i in slides"
class="slide-animation"
href="{{i.href}}"
target="_blank"
ng-hide="!isCurrentSlideIndex($index)"
ng-class="{'active':isCurrentSlideIndex($index)}">
<img src="{{i.img}}" alt="{{i.title}}" />
</a>
I'm following this tutorial here http://onehungrymind.com/build-sweet-photo-slider-angularjs-animate/ and it uses TweenMax animate the sides.
Everything was working great with one slide show on the page but I have each split into separate directives being included on the same page and the problem is that $index is conflicting. The partner logo animation moves faster than the header slide show and also has a larger array so you can imagine the problem when the header's $index gets overwritten by the other and is out of range, etc... What can I do to isolate $index so the two slideshows don't step on each other?
It's not $index that is the problem, it is the shared scope whereby each directive is sharing the parent controller scope.
$index is actually isolated as it only exists in each child scope of ng-repeat
So whatever isCurrentSlideIndex($index) is doing in controller will be shared by both instances
The simple solution is to use an isolated scope in directive so both are separate instances.
Just move the methods used in controller to directive. You can then use one attribute in directive to receive the images in the isolated scope from the parent controller
Alright I found an article that helped me realize a solution to the problem. http://codeutopia.net/blog/2014/11/10/angularjs-best-practices-avoid-using-ng-repeats-index/
Overall the moral of the story is not to use $index because it can be problematic. I chose to modify the view to pass the image object and check for that against the current index.
HTML
<div layout="row">
<div flex="100" flex-gt-sm="70" hide-xs class="header-slider center">
<img
ng-repeat="j in headerSlides"
class="header-slide-animation"
src="{{j.img}}"
ng-hide="!isCurrentHeaderSlide(j)"
ng-class="{'active':isCurrentHeaderSlide(j)}"/>
</div>
This is what the JavaScript was
$scope.isCurrentSlideIndex = function (image) {
return $scope.currentIndex === index;
};
This is what I changed it to
$scope.isCurrentHeaderSlide = function (slide) {
return $scope.headerSlides[$scope.currentIndex] == slide;
};
And it works :-)

Trigger function on a certain element - ngrepeat - angularjs

Good morning,
I'm trying to change the limitTo filter on a certain list, my issue is:
when I click to the trigger who change the filter limit the filter changes on all ng-repeated categories.
my function inside the main controller
$scope.showMore = function(limit) {
if($scope.limitItems === $scope.itemsPerList) {
$scope.limitItems = limit;
$scope.switchFilterText = 'less';
} else {
$scope.switchFilterText = 'more';
$scope.limitItems = $scope.itemsPerList;
}
}
my scenario (I rewrote it in a simplified version)
<li ng-repeat="item in category.items | limitTo: limitItems ">
{{item.title}}
</li>
<li ng-if="limitItems < (category.items.length)">
<a ng-click="showMore(category.items.length)" >Show {{ switchFilterText }}</a>
</li>
Could you explain me what's wrong with me?
I searched how to select a single element to apply the function but I didn't find anything useful
Update:
I found the way to solve my issue in this way:
No functions inside the controller are involved to make this functionality works properly:
<li ng-repeat="category in maincategories" ng-init="limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: limitItems "> {{item.title}}
</li>
</ul>
<a ng-click="limitItems = category.items.length" href>
<b ng-if="category.items.length > maxItemsPerList && limitItems != category.items.length "> Show more </b>
</a>
I'm not really convinced about Angular (I used it in my past and I was impressed by the performance but now I can see logics senseless):
What I learned:
ng-if and ng-click cannot be used in the same content because ng-if creates new scopes so if you put ng-if on top of the "show more" link it will break the code
ng-init cannot be used in the same element of the ng-repeat otherwise the var initialised will not be available inside the ng-repeat block
I think there is another way to do that, maybe more clean but in this specific case I can't do a lot.
ng-if and ng-click cannot be used in the same content because ng-if
creates new scopes so if you put ng-if on top of the "show more" link
it will break the code
Yes, ng-if creates a new scope, but it is possible to mix ng-if and ng-click (and most other directives). To do that, you'll be safer if you always write to atributes of another object instead of a simple variable. It is plain JavaScript prototypal inheritance in play.
<li ... ng-init="category.limitItems = maxItemsPerList">
ng-init cannot be used in the same element of the ng-repeat otherwise
the var initialised will not be available inside the ng-repeat block
True, in the sense that variables are created in the local scope. But again, refer to an object.
I think there is another way to do that, maybe more clean but in this
specific case I can't do a lot.
You don't need to do a lot, it is quite simple to do it right actually.
Some advices:
Use ng-init with care. I know it will tempt us but always try to put logic inside controllers and services;
Avoid assignments inside templates;
Learn how to use controllerAs syntax. It gives you an object to write your models to (the controller), so solves most problems related to scope inheritance;
Do not inject $scope, put your view models inside controllers.
Full code goes like this:
<li ng-repeat="category in maincategories" ng-init="category.limitItems = maxItemsPerList">
{{category.title}}
<ul>
<li ng-repeat="item in category.items | limitTo: category.limitItems "> {{item.title}}
</li>
</ul>
<a ng-if="category.items.length > maxItemsPerList && category.limitItems != category.items.length" ng-click="category.limitItems = category.items.length" href>
<b> Show more </b>
</a>

angularjs function in parent directive not getting called from transcluded html

I have created a dropdown list like feature using angualrjs directive, The directive is working somewhat but not in correct way I expected.
Following are the issues which I am facing.
The drop-down list alignment is correct for static but not for dynamic
I am not able to select any of the option list since getValue function which I have defined in the parent directive is not been invoked from the trancluded directive
Can anyone please tell me some solution for this
My code is as given below
PLUNKER
<div ng-controller="MainCtrl">
Static
<grant-parent ng-model="grand1">
<parent label="Parent1" value="12">
<child value="56" label="Child1">Child1</child>
<child value="57" label="Child2">Child2</child>
</parent>
<parent label="Parent1" value="13">
<child value="58" label="Child3">Child3</child>
<child value="59" label="Child4">Child4</child>
</parent>
</grant-parent>
Dynamic
<grant-parent ng-model="grand2">
<parent ng-repeat="parent in data" label="{{parent.parentName}}" value="{{parent.parentId}}">
<child ng-repeat="child in parent.childrens" label="{{child.name}}" value="{{child.id}}">{{child.name}}</child>
</parent>
</grant-parent>
</div>
Transclusion and ng-repeat have caused me headaches, and I thought it would be challenging, but the solution proves quite simple:
Remove the DOM manipulation from your link function and do the transclusion in the template!
I.e. <div ng-transclude></div> in the template of the parent and remove this line: elm.find('div').replaceWith(transclude()).
Forked plunk: http://plnkr.co/edit/UGp6D29yxnu0uJwD1BM2?p=preview
Now the markup comes out a bit different, the wrapper <div> still exists. Although there seems to be no visual difference, this may not be what you want. I do not think there is a sane way to get around this, so I would suggest altering the layout a bit: Why don't you place the children inside the parent <li>, e.g. as:
<li>
<b><a href='#' ng-click='getValue(optGroupLabel,optGroupValue)'>{{optGroupLabel}}<span class='value'>{{optGroupValue}}</span></a></b>
<div ng-transclude></div><!-- the div is now inside the li -->
</li>
This works in the plunker, but the markup is still invalid (<li> within <div>).
The best solution is to wrap the children in their own <ul>, i.e.:
<li>
<b><a href='#' ng-click='getValue(optGroupLabel,optGroupValue)'>{{optGroupLabel}}<span class='value'>{{optGroupValue}}</span></a></b>
<ul ng-transclude></ul><!-- The div is replaced with ul -->
</li>
This does not work in the plunk as it is, but should work with a little CSS tweaking.
Concerning getValue You have gotten wrong how isolated scopes and transclusion work. The grandParent directive defines the getValue method in its isolated scope. The transcluded things (the parent and child directives) get the outer scope, i.e. the scope of the MainCtrl. A solution is to move getValue() to the MainCtrl.
A better solution would be to pass a callback to the descendants of the grandparent, e.g. as scope: { assignValue: '&' }. But this solution cannot be implemented for the code in its current form because the grandparent does not directly include its children, so it cannot pass arguments to them.
The final solution - copied from the comments: move getValue to the controller of grandParent, have the parent and children require the grandparent and call that function. See http://plnkr.co/edit/pS9SspLaoPlqoWMYr8I0?p=preview

Setting value in controller works, but setting value in angular expression doesn't?

The following code doesn't work -
<div ng-init="selected=-1">
<ul ng-repeat="x in arr">
<li ng-click="selected = $index">...</li>
</ul>
When I click on one of the lis, the variable selected remains as -1. But the following does work -
<div ng-init="selected=-1">
<ul ng-repeat="x in arr">
<li ng-click="setTo($index)">...</li>
</ul>
$scope.setTo = function(index){selected = index;}
Why is that? Nothing functional seems to have changed.
ng-repeat directive creates it's own scope for each item in arr, so when expression selected = $index is executed, the new property selected is created on that scope, at the same time parent scope remains untouched.
Is why your selected property does not change in the first case.
Since the ngRepeat directive creates its own scope, you need to refer to $parent.selected in the first example:
<li ng-click="$parent.selected = $index">
http://plnkr.co/edit/9iUgp57KwvrlC3TDO3YC?p=preview

accessing parent controller from ng-repeat

I have been learning angular and found this when trying to access the parent controller
http://jsfiddle.net/eqb23s8t/
I was expecting to access the same variable from the parent controller from inside the ng-repeat using the $parent so when one of the checkbox is pressed, all should be updated, but this is not true. Why ?.
<div ng-app ng-controller="ParentCtrl">
<ul>
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-checked="$parent.somevar" /></li>
</ul>
</div>
First, your jsFiddle has a ChildCtrl defined but it will have no effect because you never use it. You can delete it.
Second, as described in the ngChecked documentation, there is a difference between ngChecked and ngModel:
https://docs.angularjs.org/api/ng/directive/ngChecked
If what you're expecting to happen is have all the checkboxes check/uncheck together, you probably want ngModel rather than ngChecked.
It's not clear from your question what you're actually trying to do, but here's a fork of your jsFiddle illustrating data sharing through a $parent variable:
http://jsfiddle.net/7jzyk7f6/
It just does the following:
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-model="$parent.somevar" /></li>
to illustrate both concepts.
You are not bind a model for the view, which can reflect the changes. The current code just reads the model (in this case the somevar) state. Use ng-model instead of ng-checked. The angular will handle the rest:
HTML
<div ng-app ng-controller="ParentCtrl">
<ul>
<li ng-repeat="city in cities">{{city}}<input type="checkbox" ng-model="$parent.somevar" /></li>
</ul>
</div>
JS
function ParentCtrl($scope) {
$scope.cities = ["NY", "Amsterdam", "Barcelona"];
$scope.somevar = true;
}
http://jsfiddle.net/eqb23s8t/4/
In this case, you need to use ngModel (two-way binding) instead of ngChecked (one-way binding):
ng-model="$parent.somevar"
See JSFiddle
I'm also totally newbie in Angular so I can be wrong. But I see few... things in your code. First of all ChildCtrl is not used at all. Second, it looks like only ng-model directive applied two-way binding to checkboxes. Yet ng-checked used just to add|remove checked attribute.
And js-fiddle
Sir please use ng-model to refer scope of the parent.
here is what i have created demo for you [demo][1]
[1]: http://jsfiddle.net/nwg7bwLx/

Categories

Resources