ng-class not responding to change in model in Angular? - javascript

Having searched extensively, got some help from A ternary in templates and getting partially functional results, I'm 99% sure I'm doing it 99% right, but...obviously not completely, so I'm here.
The basic HTML structure:
<ul>
<li class="week-row" ng-repeat="week in weeks" id="{{$index}}">
<ul class="tiles" ng-class="{ 'expanded': week.isOpen, 'hidden': !week.isOpen }">
<li class="day-tile"
ng-class="{'active': (activeDay == $parent.$index + '-' + $index) }"
id="{{$parent.$index + '-' + $index}}"
ng-repeat="day in week.days"
ng-click="fn2({ week: $parent.$index, day: $index })">
<!-- moved ng-class to here, and it works? -->
<div>some stuff in here</div>
</li>
</ul>
</li>
</ul>
The stuff in the controller that sits above it:
$scope.activeDay = null;
$scope.fn1 = function() { $scope.activeDay = '0-0'; }; // this works, and sets the first one active
$scope.fn2 = function(data) { $scope.activeDay = data.week + '-' + data.day; }; // this sets the first one not active, but none of the others go active
I'm trying to set one of the nested list items to be active, based on its indexes in the nested arrays by using the $parent.$index and $index as a string, joined by '-'.
What's throwing me off is that console.logging $scope.activeDay, data.week + '-' + data.day, and both == and === comparisons come out exactly as I would expect, (the same strings, true, true) and it works on initial load when I set activeDay to '0-0', so I'm clearly missing something in my binding or...?
After finding this: https://egghead.io/lessons/angularjs-the-dot - I tried setting it up as an object so I couldn't get into some weird isolate scope nonsense: $scope.tiles = { activeDay: null }; and then setting that property from my functions, to no avail.
Why does setting it initially work, while changing it later does not? Is this improper binding of classes or...?
I also tried it with
class="day-tile {{activeDay == $parent.$index + '-' + $index ? 'active' : ''}}"
and that works initially, but breaks afterwards...
Your help is much appreciated!
UPDATE:
After moving ng-class="{'active': (activeDay == $parent.$index + '-' + $index) }" onto the div inside the ng-repeated .day-tile li, it works fine.
Any ideas why this might be?

From the info you provided I have this working jsFiddle.
I removed the classes you were hard coding to true and stuck them in class="". The classes you were evaluating expressions for i put in ng-class="".
I attached your functions to $scope so ng-click could find them. So $scope.fn2 rather than var fn2.
Without knowing more details, I would say this should fix your problem.
Code Changes:
controller:
$scope.activeDay = null;
$scope.fn1 = function() { $scope.activeDay = '0-0'; };
$scope.fn2 = function(data) { $scope.activeDay = data.week + '-' + data.day; };
partial:
<ul class="tiles" ng-class="{'expanded' : week.isOpen, 'hidden' : !week.isOpen}">
<li class="day-title"
ng-class="{'active': (activeDay == $parent.$index + '-' + $index)}"
id="{{$parent.$index + '-' + $index}}"
ng-repeat="day in week.days"
ng-click="fn2({ week: $parent.$index, day: $index })">
//put expression here so you can see the list item
{{week.day}}
</li>
</ul>

Related

Angular 1.4 - How to move logic for ng-click item from template to controller?

I am looking to modify some code to add more functionality to an ng-click event that is within an ng-repeat.
The current code updates the selected property for the item in the list:
<li ng-repeat="value in facet.values" ng-click="value.selected = (value.selected == 0) ? 1 : 0; accountSearch()">
...
</li>
I'd like to update the selected property but also fire an event to our analytics provider:
<li ng-repeat="value in facet.values" ng-click="itemClicked(value)">
...
</li>
Then in my controller:
$scope.itemClicked = function(value){
value.selected = (value.selected == 0) ? 1 : 0; $scope.accountSearch();
// do other things
}
The issue is that the above code will not actually modify the selected property in the template. How can I recreate this behavior but move the logic to the controller?
The reason why it's not updating is that it's probably not bound to the model anymore. Try this:
<li ng-repeat="value in facet.values" ng-click="itemClicked(value)">
<input type="checkbox" ng-model="value.selected" />
</li>
You may want to use ng-checked instead of ng-model based on your use case
Value is not returned to the template, so it's lost. Try the following:
<li ng-repeat="value in facet.values" ng-click="itemClicked($index)">
...
</li>
$scope.itemClicked = function(index){
$scope.facet.values[index].selected = ($scope.facet.values[index].selected == 0) ? 1 : 0;
$scope.accountSearch();
}

How can I correctly use variable variable names in ng-repeat in angular?

HTML:
<ul>
<li ng-repeat="comic in comiclist">
<span>{{ comic.title }} : {{comic.id}}</span>
<div >
<img ng-src="{{comic.thumbnail.path}}.{{comic.thumbnail.extension}}"/>
</div>
<div>
<ul>
<li ng-repeat="character in charlist + {{comic.id}}">
<span>{{character.name}}</span>
</li>
</ul>
</div>
</li>
</ul>
JS with angular
var App = angular.module('MarvelApp', []);
App.controller('MainController', function($scope,$window, $http) {
$scope.GetSeries = function(){
$http.get('http://gateway.marvel.com/v1/public/series?')
.then(function(response){
$scope.serieslist = response.data.data.results;
});
};
$scope.GetComics = function(){
$http.get('http://gateway.marvel.com/v1/public/series/' + $scope.selectedseries + '/comics?')
.then(function(response){
$scope.comiclist = response.data.data.results;
});
//comic in comiclist contains comic.id
//which needs to go to GetCharacter()
}
$scope.GetCharacter = function(comicid){
$http.get('http://gateway.marvel.com/v1/public/comics/' + comicid + '/characters')
.then(function(response){
$scope.['charlist' + comicid] = response.data.data.results;
//this list needs to be displayed in the second ng-repeat
});
};
});
I'd like to get the character list to display in the right div. How I had it set up before, (without $scope.['charlist' + comicid]) it was overriding the other ng-repeats too.
Also, whenever GetComics() gets called it does it automatically.
I don't think $scope.[] is valid syntax (unless I've been missing out on a nifty trick).
You should instead name an "associative array" under $scope something like:
$scope.charlist[comicid] = ... ;
Your ng-repeat would then look something like:
<li ng-repeat="character in charlist[comic.id]">
EDIT As mentioned in the comments, $scope.charlist must be defined before the above lines can be used. This can happen in a few ways:
Make a check before you set it: if(!$scope.charlist) $scope.charlist = [];
Define it somewhere in the controller with $scope.charlist = [];
Of course there's any number of ways you can do this, but these make these I believe make the most sense, the first of which catches invalid states i.e. if for some reason $scope.charlist is set to null it would create a new "associative array".

AngularJS: ng-click for ng-repeat to get all data at once

I have my simple ng-repeat:
<div ng-repeat="x in names">
<h4>{{x.productid}}</h4>
<h4>{{x.newquantity}}</h4>
<h4>{{x.total}}</h4>
<button ng-click="addInfoAboutOrder(x)">Add Info</button>
</div>
and AngularJS function:
$scope.addInfoAboutOrder = function(x) {
$scope.productId = x.productid;
$scope.productQuantity = x.newquantity;
$scope.total = x.total;
$http.post("api/setOrdersInfo" + "/" + $scope.productId + "/" + $scope.productQuantity + "/" + $scope.total)
}
At the moment it is working. However, ng-repeat prints out button as many times as there are data(from 1 to 50). Of course this can be done by adding a simple filter, but it does not solve my problem. The biggest problem is it adds only one row in the table in the database. My problem is I can't make one
<button ng-click="addInfoAboutOrder(x)">Add Info</button>
to handle all my data in ng-repeat. I would like by one button click to add as many data in database as there are in ng-repeat.
Move the button outside of the ng-repeat:
<div ng-repeat="x in names">
<h4>{{x.productid}}</h4>
<h4>{{x.newquantity}}</h4>
<h4>{{x.total}}</h4>
</div>
<button ng-click="addInfoAboutOrder(names)">Add Info</button>
Then pass in names instead of x and iterate over names in order to get all the data out of it, making a $http request for each iteration (do note that this will most likely cause performance issues for large data sets, so you should try to find a better solution to do what you want):
$scope.addInfoAboutOrder = function(names) {
for (var i = 0; i < names.length; i++) {
$http.post("api/setOrdersInfo" + "/" + names[i].productid + "/" + names[i].newquantity + "/" + names[i].total);
}
}

Reusable directive with dynamic ng-repeat item name

I have created reusable directive something like dropdown but dropdown open in modal which is working good.
my directive looks like this
<p-select items="deptStations" header-text="Select " text="Select departure..." text-icon="ion-chatbubble-working" text-field="City_Name_EN" text-field2="City_Code" value-field="City_Code" ng-model="deptStation.value">
</p-select>
<p-select items="arrStations" header-text="Select " text="Select arrival..." text-icon="ion-chatbubble-working" text-field="D.City_Name_EN" text-field2="D.City_Code" value-field="D.City_Code" ng-model="arrStation.value">
</p-select>
My directive html is
<ion-content>
<div class="list">
<label ng-repeat="item in items | filter:search" class="item item-text-wrap" ng-click='validateSingle(item)'>
{{item[textField]}} {{textField2 !== '' ? " (" + item[textField2] + ")" : ""}}
</label>
</div>
</ion-content>
Now my issue is when JSON is 1 level it will work as below
[{City_Name_EN:'Abu Dhabi', City_Code:'AUH' },
{City_Name_EN:'Alexandria',City_Code:'HBE' }]
But if I have 2 level JSON than it will not work
[{D:{City_Code:'AMM',City_Name_EN:'Amman'},
D:{City_Code:'BKK',City_Name_EN:'Bangkok'}}]
So how can make this part dynamic {{item[textField]}}
My plunkr http://plnkr.co/edit/GxM78QRwSjTrsX1SCxF7?p=preview
With this kind of dynamic expression of yours, it is always better to have directive consider only a specific contract provided as view model. If the directive consumer has a different data format it should be upto that component to provide the contract that directive needs, it can just map the data to the view model that directive expects. This way you can keep things clean, that would be my opinion.
Now to work around your issue you would need to do a trick to evaluate the multilevel property against an object. You can use $scope.$eval to evaluate any dynamic expression against the scope object. example you can evaluate a dynamic property evaluation of prop1.prop2.prop3 on a scope property item by doing $scope.$eval("prop1.prop2.prop3", item) or $scope.$eval("item." + "prop1.prop2.prop3")
So in your directive:
Added a scope function to get the item text and value:
$scope.getItemName = function(field, item){
//here "this" represents the current child scope of ng-repeat
return $scope.$eval(field, item);
//return this.$eval("item." + field);
}
and
$scope.validateSingle = function(item) {
$scope.text = $scope.$eval($scope.textField, item) + ($scope.textField2 !== '' ? " (" + $scope.$eval($scope.textField2, item) + ")" : "");
$scope.value = $scope.$eval($scope.valueField, item);
...
Update your template to get respective text:
<label ng-repeat="item in items | filter:search" class="item item-text-wrap" ng-click='validateSingle(item)'>
{{getItemName(textField, item)}} {{textField2 !== '' ? " (" + getItemName(textField2, item) + ")" : ""}}
</label>
Plnkr

How to use ng-repeat with filter and $index?

I want to use ng-repeat in Angular, while I only want to output some elements of the array. An Example:
ng-repeat="item in items | filter:($index%3 == 0)"
However, this does not work. Please tell me how to do this; only output exact index of elements.
In your code, filter apply on 'items' array, not on each array item, that's why it does not work as you expect.
Instead, you can use ng-show (or ng-if):
<ul>
<li ng-repeat="item in items" ng-show="$index % 3 == 0">{{item}}</li>
</ul>
See: http://jsfiddle.net/H7d26
EDIT: Use ng-if directive if you do not want to add invisible dom elements:
<ul>
<li ng-repeat="item in items" ng-if="$index % 3 == 0">{{item}}</li>
</ul>
Create a custom filter, for example:
filter('modulo', function(){
return function (arr, div, val) {
return arr.filter(function(item, index){
return index % div === (val || 0);
})
};
});
Then change the ng-repeat to:
<ul ng-repeat="item in items | modulo:3">
Or filter by (index % 3 === 1) like:
<ul ng-repeat="item in items | modulo:3:1">
http://jsfiddle.net/Tc34P/2/
What you need to do is find the index of "item" in $scope.items. See below
ng-repeat"item in items | filter:(items.indexOf(item)%3 == 0)
So a real world example (for others who come across this solution) would be.
<div ng-repeat="item in filtered = (items | filter:customfilter | orderBy:customsort)">
<div> This is number {{items.indexOf(item)}} in the items list on the scope.</div>
</div>
The above example will get the correct position regardless of the sorting or filtering
All the precedent answers will work, but if you want to use a filter, you can also define it yourself, like in this fiddle : http://jsfiddle.net/DotDotDot/58y7u/
.filter('myFilter', function(){
return function(data, parameter){
var filtered=[];
for(var i=0;i<data.length;i++){
if(i%parameter==0)
filtered.push(data[i]);
}
return filtered;
}
});
and then call it like this :
ng-repeat='item in items | myFilter:3'
(I added extra complexity with a parameter, if you want to change it quickly to even numbers for example)
Have fun
++
I suggest filtering with a function:
ng-repeat"item in filterItems(items)"
And on the Controller:
$scope.filterItems= function(items) {
var result = {};
angular.forEach(items, function(item) {
// if item is something.. add to result
});
return result;
}
An alternate approach to solving this creates a new "list" (I don't know the proper term in this context) in the ng-repeat. This new list can then be referred to within the scope
<ul class="example-animate-container">
<li class="animate-repeat" ng-repeat="friend in filteredFriends = ((friends | filter:q:strict) | orderBy:'age')" ng-if="$index % 2 == 0">
[{{$index + 1}}] {{filteredFriends[$index].name}} who is {{filteredFriends[$index].age}} years old.
<span ng-if="$index+1 < filteredFriends.length">[{{$index + 2}}] {{filteredFriends[$index+1].name}} who is {{filteredFriends[$index+1].age}} years old.</span>
</li>
</ul>
The ng-if on the span wrapping the second half prevents the base text from being shown when the previous element is the last element of the list.

Categories