Trigger function on a certain element - ngrepeat - angularjs - javascript

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>

Related

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

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.

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

AngularJS - passing variable to function returns preprocessed syntax

I want to send an AngularJS variable to a function in my controller. Here's a sample snippet of my code:
<ul>
<li ng-repeat="score in scores | filter:search | orderBy : 'first'">
<span class='popup' ng-click='popup("/scores/create/{{ score.user_id }}")'>Add Score</span>
</li>
</ul>
When I inspect the ng-click attribute in my browser, I see the processed variable I need: "/scores/create/60".
But when I click on the element and the function is fired, it returns the preprocessed AngularJS code: "/scores/create/{{ score.user_id }}", thus breaking my function. Is there a way around this?
You are already "in" angular, so you don't need to use {{}}, you just have to get out of the string you are passing in:
ng-click='popup("/scores/create/" + score.user_id)'

Angular JS: ng-switch on boolean not working

I am trying to conditionally display a directive based on a boolean value stored in the parent scope. I can't figure out why the below does not work. By, "not work" I mean neither directives are displayed.
<ul class="nav navbar-nav pull-right" ng-switch="userIsAuthenticated">
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when="false"></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when="true"></account-item>
</ul>
Neither directives are shown even thought "userIsAuthenticated" is set to 'false' in my test case. If I add {{userIsAuthenticated}} above the directives 'false' is output as expected.
I've also tried this:
<ul class="nav navbar-nav pull-right" ng-switch={{userIsAuthenticated}}>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in anonymousMenuItems" ng-switch-when={{false}}></account-item>
<account-item item="accountMenuItem" ng-repeat="accountMenuItem in authenticatedMenuItems" ng-switch-when={{true}}></account-item>
</ul>
If I remove the conditional ng-switch-when attribute from either of the directives they will display. So I'm know the problem is my ng-switch.
Your usage of ng-switch works in this simplified demo, of course without your account-item directive:
http://plnkr.co/AppN8xmFeIwjaP631lj7
Without seeing the code for account-item, it is hard to guess what might be interfering with it. You might consider using ng-if to handle displaying one item or another.
<ul>
<div ng-if="!userIsAuthenticated">Content when not authenticated</div>
<div ng-if="userIsAuthenticated">Content when authenticated</div>
</ul>
Update
Also make sure you bind to an object property, instead of a primitive boolean. Like: user. authenticated
Since ngSwitchWhen has a priority of 800, you need to set a higher priority to your custom directive (i.e. account-item) in order for it to be compiled before being process by the ngSwitchWhen directive. E.g.:
.directive('accountItem', function () {
return {
...
priority: 900,
...
};
});

Bind once in angular... and then again on each event

I've got a 400kb picture in a data url in my scope. I'm using <img ng-src='{{dataUrl}}'/>, which is working, but extremely slowly: I assume angular is checking this 400kb value against itself every single frame. The angularjs batarang confirms that 95% of my total run time is spent $watching {{dataUrl}}.
The dataUrl CAN change dynamically, but certainly not every frame - only when the user selects a new image.
How can I blast a new dataUrl into the DOM on events without using the full two-way binding features that are becoming so expensive?
The $watch function returns a callback just for that. You just have to execute it back to destruct the watcher.
var watcher = $scope.$watch('data', function(newValue, oldValue) {
if (newValue) {//you could do a isset here
watcher();
}
});
To re-watch. Might be expensive depending on the number of items on the current $scope
if(!$scope.$$phase) {
$scope.$digest()
}
You can use bindonce - a high performance one time binding for AngularJS that can render your results in 0 watches.
Example:
<ul>
<li bindonce ng-repeat="person in Persons">
<a bo-href="'#/people/' + person.id"><img bo-src="person.imageUrl"></a>
<a bo-href="'#/people/' + person.id" bo-text="person.name"></a>
<p bo-class="{'cycled':person.generated}" bo-html="person.description"></p>
</li></ul>
or:
<div bindonce="Person" bo-title="Person.title">
<span bo-text="Person.firstname"></span>
<span bo-text="Person.lastname"></span>
<img bo-src="Person.picture" bo-alt="Person.title">
<p bo-class="{'fancy':Person.isNice}" bo-html="Person.story"></p>
Update:
another One time binding for AngularJS as suggested by Ilan Frumer
Example:
<ul>
<li ng-repeat="user in users">
<a once-href="user.profileUrl" once-text="user.name"></a>
<a once-href="user.profileUrl"><img once-src="user.avatarUrl"></a>
<div once-class="{'formatted': user.description}" once-bind="user.description"></div>
</li></ul>
You can use one-way-databiding, like this
<img ng-scr="{{::dataUrl}}"/>
Or if you are using an angular version bigger than 1.3 there is an one bind embedded. That you can use on almost any directive. The $digest list will handled the watches only once, after loaded they will be destroyed.

Categories

Resources