Angular. Why filter invokes automatically? - angularjs

I'm new in angular and I reeding A.Freeman's book "Pro Angular JS".
So I stuck in one of examples trying to understand why filter in ng-repeat is triggered.
Here is the code:
<body ng-controller="sportsStoreCtrl">
<div class="navbar navbar-inverse">
<a class="navbar-brand" href="#">SPORTS STORE</a>
</div>
<div class="panel panel-default row" ng-controller="productListCtrl">
<div class="col-xs-3">
<a ng-click="selectCategory()" class="btn btn-block btn-default btn-lg">Home</a>
<a ng-repeat="item in data.products | orderBy:'category' | unique:'category'" ng-click="selectCategory(item)" class=" btn btn-block btn-default btn-lg">
{{item}}
</a>
</div>
<div class="col-xs-8">
<div class="well" ng-repeat="item in data.products | filter:categoryFilterFn">
<h3>
<strong>{{item.name}}</strong>
<span class="pull-right label label-primary">
{{item.price | currency}}
</span>
</h3>
<span class="lead">{{item.description}}</span>
</div>
</div>
</div>
</body>
and
angular.module("sportsStore")
.controller("productListCtrl", function ($scope, $filter) {
var selectedCategory = null;
$scope.selectCategory = function (newCategory) {
selectedCategory = newCategory;
}
$scope.categoryFilterFn = function (product) {
return selectedCategory == null ||
product.category == selectedCategory;
}
});
categoryFilterFn is one that confuses me. Why it's invoking when I press catefory buttons (with selectCategory() method on ng-click) since I never call categoryFilterFn explicitly?

Answering you question - because of $digest. You don't have call categoryFilterFn directly. Your selectedCategory has changed which is used in categoryFilterFn and categoryFilterFn is bound to scope.

Not sure how I can describe it correctly but here my explanation.
There are two "independent" parts :
The repeat iterate over an array of items.
If you select an category via ng-click function you set the new category in the scope.
Here kicks the filter function in, witch ties it up.
It is triggered because a new category is selected ($digest) and "reordering" the array (like map function in plain Javascript) and the angular magic (ng-repeat) displays only items with this category.
And that's the reason why I love angular so much 🤓

Related

Refreshing ng-repeat list after adding an element

I have a list of orders that I sort based on status key. Then I display it using ng-repeat in bars. I can click Accept button to move one order from submitted to accepted. The question is: How do I refresh the bar displaying accepted orders?
HTML
<div class="bar" ng-controller="AdminCtrl">
<li ng-repeat="order in submitted">
<div >
<p >{{order.name}} </p>
<p>{{order.total}}</p>
<button class="btn" ng-click="acceptOrder(order)">Akceptuj</button>
</div>
</li>
</div>
<div class="bar" ng-controller="AdminCtrl" >
<li ng-repeat="order in delivery">
<div >
<p >{{order.name}} </p>
<p>{{order.total}}</p>
<button class="btn" ng-click="addOrderToDeliveryQueue(order)">Dodaj do kolejki dostawy</button>
</div>
</li>
</div>
</div>
JS
$scope.submitted = [];
$scope.accepted = [];
$scope.delivery = [];
angular.forEach($scope.orders, function(value, key) {
if (value.status == 'submitted')
$scope.submitted.push(value);
if (value.status == 'accepted')
$scope.accepted.push(value);
if (value.status == 'delivery')
$scope.delivery.push(value);
});
$scope.acceptOrder = function(order) {
var index = $scope.submitted.indexOf(order);
order.status = 'accepted';
$scope.accepted.push(order);
$scope.submitted.splice(index, 1);
AngularJS handles refreshing ng-repeat directives automatically when it sees a change in the collection. You are not seeing this behavior because you are actually creating multiple independent instances of your AdminCtrl controller. Each of your divs has this: ng-controller="AdminCtrl". This creates a new instance of the AdminCtrl scoped to that div. What you really want is one instance of that AdminCtrl. You can achieve this by moving the ng-controller directive to the outermost container element.
<div ng-controller="AdminCtrl">
<div class="bar">
<li ng-repeat="order in submitted">
// your markup
</li>
</div>
<div class="bar">
<li ng-repeat="order in accepted">
// your markup
</li>
</div>
</div>
// etc.

AngularJS ng-if ng-show/hide within ng-repeat hiding all divs

I have seen similar questions related to this but this is different. In most of the questions, ng-hide/show fired on ng-click event.
Here is the code.
$scope.showDetails = 0;
$scope.delete = function(event) {
alert(event.target.id);
$scope.showDetails = 1;
//There would be more code for delete function. Some Ajax calls will be here.
}
<div ng-repeat="suggestions1 in suggestions">
<div class="col-xs-12 alert alert-info" ng-if="showDetails == '0'">
<center>
<a ng-click="delete($event)" id={{suggestions1.id}} class = "btn btn-danger">
<font size = "4">
<i class="fa fa-times-circle"></i>
</font>
Delete
</a>
</center>
</div>
</div>
Problem is when I click on the button it hides all the divs. I am expecting one div to hide but it is hiding all the div inside ng-repeat.
I have searched for multiple questions and tried the solutions but the issue still persists.
In that case you need to have a property named showDetails in each object of your new_suggestions array and enable ng-if based on that.
<div ng-repeat="suggestions1 in news_suggestions">
<div class="col-xs-12 alert alert-info" ng-if="suggestions1.showDetails == '0'">
Your code is logically wrong. You are keeping only one copy of showDetails variable. You need some property which is related to each object.
Try like this
$scope.delete = function(index) {
$scope.news_suggestions[index].hideDetails = true;
}
<div ng-repeat="suggestions1 in news_suggestions">
<div class="col-xs-12 alert alert-info" ng-hide="suggestions1.hideDetails">
<center> <a ng-click="delete($index)" id={{suggestions1.id}} class = "btn btn-danger"> <font size = "4"><i class="fa fa-times-circle"></i></font> Delete </a> </center></div></div>
There's one more approach:
// add an array
$scope.hiddenIds = [];
<div ng-repeat="suggestions1 in news_suggestions">
<!-- check if hidden -->
<div ng-hide=" hiddenIds.indexOf(suggestions1.id)>-1 " class="col-xs-12 alert alert-info">
<center>
<!-- remove/add to hidden array on click -->
<a ng-click=" hiddenIds.indexOf(suggestions1.id)>-1 ? hiddenIds.splice(hiddenIds.indexOf(suggestions1.id),1) : hiddenIds.push(suggestions1.id) " id={{suggestions1.id}} class = "btn btn-danger">
Here you don't modify the existing collection of elements.
Hint: if you hide the show/hide button, you won't be able to show the element back again. So you probably want to change your html layout a bit ;)
EDIT
Version 2:
You can always move it from your html to the scope.
// add an array
$scope.hiddenIds = [];
$scope.checkHide = function(suggestions1){
return $scope.hiddenIds.indexOf(suggestions1.id)>-1;
};
$scope.clickHide = function(suggestions1){
$scope.hiddenIds.indexOf(suggestions1.id)>-1 ? $scope.hiddenIds.splice($scope.hiddenIds.indexOf(suggestions1.id),1) : $scope.hiddenIds.push(suggestions1.id) ;
};
<div ng-repeat="suggestions1 in news_suggestions">
<!-- check if hidden -->
<div ng-hide="checkHide(suggestions1)" class="col-xs-12 alert alert-info">
<center>
<!-- remove/add to hidden array on click -->
<a ng-click="clickHide(suggestions1)" id={{suggestions1.id}} class = "btn btn-danger">
EDIT 2
If you don't need that element in news_suggestions (you're not planning to show it back again), you can simply remove it, which is even easier :)
$scope.remove = function(i){
$scope.news_suggestions.splice(i,1);
};
<div ng-repeat="suggestions1 in news_suggestions track by $index">
<div class="col-xs-12 alert alert-info">
<center>
<!-- remove by index on click -->
<a ng-click="remove($index)" id={{suggestions1.id}} class = "btn btn-danger">
JSBin
Yes, something wrong with your logic. Try this:
$scope.delete = function(el){
el.hideDetails = true;
}
<div ng-repeat="suggestions1 in news_suggestions">
<div class="col-xs-12 alert alert-info" ng-hide="hideDetails">
<center> <a ng-click="delete(this)" id={{suggestions1.id}} class = "btn btn-danger"> <font size = "4"><i class="fa fa-times-circle"></i></font> Delete </a> </center></div></div>

AngularJS - Enable and disable button

I have a condition and it is only on selecting an image among the optional images the next button needs to get enabled and also next should not get enabled on last outer select.
The problem here is on selecting the first image, the button is getting enabled and the scope is assigning the value to false and it is not getting true for the second iteration of images. How can I fix this issue?
<div class="col-xs-6 col-md-10 col-lg-7">
<div ng-repeat="outer in outers track by $index">
<div ng-if="outer_index== $index">
<div ng-bind="::outer.label"></div>
<ul class="list-group">
<li class="cursorPointer" ng-repeat="inner in outer.inners | orderBy: 'id'" ng-class="{'selected': getSelectedClass($parent.$index, $index)}" class="deselected">
<div class="col-xs-6">
<div class="img">
<img ng-src="data:image/png;base64,{{inner.base64Icon}}" alt="{{inner.description}}" title="{{inner.description}}"
ng-click="process()">
<div class="desc" ng-bind="inner.description"></div></div>
</div>
</li>
</ul>
</div>
</div>
</div>
<div><button type="button" class="btn btn-info" ng-disabled="outer_index == outers.length -1 || disableNext" ng-click="getNext()">Next</button></div>
In JS code, to activate the button only if,
$scope.disableNext=true;
$scope.process = function() {
$scope.disableNext=false;
}
$scope.getNext = function (){
$scope.outer_index = $scope.outer_index + 1;
$scope.datas = $scope.outers[$scope.outer_index];
}
NOTE: I tried
ng-click="process();disableNext=false;" for this condition won't work within ng-repeat.
Just set the disableNext to true after selecting next and it works!
$scope.getNext = function() {
$scope.disableNext=true;
$scope.outer_index = $scope.outer_index + 1;
$scope.datas = $scope.outers[$scope.outer_index];
}

Repeating the nested objects of a single element with angular

Most of the answers I've found are for repeating the entire object, and that works for me without problem. What I need is to display the objects of an element in an array. Here is my controller:
var app = angular.module('QLoad', []);
app.controller('loadCtrl', function($scope) {
$scope.question=[
{Id:'1', qText:'What\'s your Gender?',aCollection:[
{text:'Male',
icon:'fa fa-male fa-5x'},
{text:'Female',
icon:"fa fa-female fa-5x"}
]
},
{Id:'2', qText:'What kind of Pain do you have?',aCollection:[
{text:'Burning',
icon:'fa fa-fire fa-5x'},
{text:'Stinging',
icon:"fa-bee.png"}
]
}];
});
As I mentioned, I could easily do two nested ng-repeats and display everything, but I only need to display the answers for each individual question. That's why I have the following two views I've tried, but it neither works:
<h1>{{question[0].qText}}</h1>
<div ng-repeat="q in question">
<div ng-repeat="answer in q.aCollection" class="content">
<div class=button>
<i class="{{q[0].answer.icon}}" aria-hidden="true"></i>
{{q[0].answer.text}}
</div>
</div></div>
Nor does this work:
<h1>{{question[0].qText}}</h1>
<div ng-repeat="answer in question[0].aCollection" class="content">
<div class=button>
<i class="{{answer.icon}}" aria-hidden="true"></i>
{{answer.text}}
</div>
</div></div>
And this is where I'm stuck. I can't display a single question object and its nested array. Thank you for your help.
You can use [limitTo][1] filter to achieve that.
<div ng-repeat="q in question | limitTo:1">
<div ng-repeat="answer in q.aCollection" class="content">
<div class=button>
<i class="{{answer.icon}}" aria-hidden="true"></i>
{{answer.text}}
</div>
</div></div>
You can define an index which will be incremented with the ng-click.
Template
<h1>{{question[questionTrackIndex].qText}}</h1>
<div ng-repeat="answer in question[questionTrackIndex].aCollection" class="content">
<div class=button>
<i class="{{answer.icon}}" aria-hidden="true"></i>
{{answer.text}}
</div>
</div>
<div class="btn btn-warning" ng-if="questionTrackIndex != 0" ng-click="previousQuestion()">Previous</div>
<div class="btn btn-warning" ng-if="question.length - 1 > questionTrackIndex" ng-click="nextQuestion()">Next</div>
Controller
$scope.questionTrackIndex = 0;
$scope.question=[
{Id:'1', qText:'What\'s your Gender?',aCollection:[
{text:'Male',
icon:'fa fa-male fa-5x'},
{text:'Female',
icon:"fa fa-female fa-5x"}
]
},
{Id:'2', qText:'What kind of Pain do you have?',aCollection:[
{text:'Burning',
icon:'fa fa-fire fa-5x'},
{text:'Stinging',
icon:"fa-bee.png"}
]
},
{Id:'3', qText:'Question no 3',aCollection:[
{text:'Answer1',
icon:'fa fa-fire fa-5x'},
{text:'Answer2',
icon:"fa-bee.png"}
]
}
];
$scope.nextQuestion = function() {
$scope.questionTrackIndex++;
}
$scope.previousQuestion = function() {
$scope.questionTrackIndex--;
}
The variable $scope.questionTrackIndex has been used to scroll through the questions. I have added two buttons next and previous to scroll through the questions. With clicking next button, $scope.questionTrackIndex has been incremented and with clicking previous button, $scope.questionTrackIndex has been decremented.
See this PLUNKER
One of solution maybe is
<div ng-repeat="q in question">
<div ng-click="a($index)">{{q.qText}}</div>
</div>
<h1>{{answ.text}}</h1>
<div ng-repeat="answer in answ.answerList" class="content">
<div class=button>
<i class="{{answer.icon}}" aria-hidden="true"></i>
{{answer.text}}
</div>
and your controller look like
$scope.answ = {};
$scope.answ.text = $scope.question[0].qText
$scope.answ.answerList = $scope.question[0].aCollection;
$scope.a = function(index){
$scope.answ.text = $scope.question[index].qText
$scope.answ.answerList = $scope.question[index].aCollection;
}
Example you can see on link

AngularJS: Empty value when access in view

I'm newbie in AngularJS, I have a question, please explain to me the reason of my mistake. At first, I have a factory name CategoryParent with this function declared like this,
routerApp.factory('CategoryParent', function($http) {
var categoryParentFactory = {};
var hostCMSAPI="http://***.***.***.***:****";
// get all categories
categoryParentFactory.allCategoryParents = function() {
console.log("call get allCategoryParents");
return $http.get(hostCMSAPI+'/api/categoryparents/');
};
.......
and a controller call this function named categoryParentController:
.controller('categoryParentController', function(CategoryParent,$scope) {
console.log("cateParent ctrl");
$scope.processing=true;
$scope.dataList=[];
$scope.getAllCategoryParents=function(){
CategoryParent.allCategoryParents().success(function(response){
$scope.processing = false;
$scope.list=response;
});
I'm using ui.router like this (nested view):
.state('home.cateParentMenu',{
url:'/cateParentMenu',
templateUrl:'categoryParentTop.html',
controller:'categoryParentController',
controllerAs:'categoryParent'
})
Parent view trigger controller function here:
The Homey Page
This page demonstrates nested views.
<a ui-sref=".list" class="btn btn-primary">List</a>
<a ui-sref=".paragraph" class="btn btn-danger">Paragraph</a>
<a ui-sref=".cateParentMenu" class="btn btn-warning" ng-click="getAllCategoryParents()">cateParentMenu</a>
and last, the children view come here
data show
{{processing}}
{{list}}
{{item.cate_parent_name}}
<ul class="nav navbar-nav" >
data show
{{processing}}
{{list}}
<li data-ng-repeat="item in list.data"><a ui-sref=".detail({cate_parent_id:item.cate_parent_id})" ng-click="getById(item.cate_parent_id)" ui-sref-active="active" id="{{item.cate_parent_id}}">{{item.cate_parent_name}}</a></li>
</ul>
<div class="col-sm-6">
<div ui-view=""></div>
<!--<div ui-view="serviceRef"></div>-->
<!--<div ui-view="categoryRef"></div>-->
</div>
I set $scope.list and $scope.processing to transfer result to view. But in view I show {{list}}, nothing appears and {{processing}} = true ???
Why? please help me, many thanks to all your suggests.
Controller
Fix $scope.dataList=[]; to $scope.list=[];

Resources