AngularJS Function Trigger Reason - angularjs

I am following the basic AngularJS tutorials at 'http://angularjs.org/' but I am slightly confused about the reason behind certain functions being triggered.
The their ToDo List app, they have the follows JS within the controller:
$scope.remaining = function() {
var count = 0;
angular.forEach($scope.todos, function(todo) {
count += todo.done ? 0 : 1;
});
return count;
};
Which is linked to the following HTML:
<div ng-controller="TodoCtrl">
<span>{{remaining()}} of {{todos.length}} remaining</span>
[ archive ]
<ul class="unstyled">
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.done">
<span class="done-{{todo.done}}">{{todo.text}}</span>
</li>
</ul>
</div>
However, when a checkbox is selected/deselected, I don't see how/where the $scope.remaining function is triggered to then update the values in the UI. There are other scope functions, but they don't seem to get called in this scenario, so what is special about this function?

The function remaining is used in an Expression (e.g. {{}}) angular creates watches for these expressions. So everytime a digest loop happens your function get called.

Related

AngularJS component use N times dynamically

I am new in AngularJS and I am using component in my module.
I have module:
angular.module("reviewApp",[]).component("applicantDetails",{
templateUrl : '../comp/Applicant_Details_Fields.html',
controller : function($scope){...}
});
At this moment I can use my component from HTML code like this
<applicant-details></applicant-details>
But now I need to change it to be more flexible. Depending on given N value it must add that component in the HTML N times.
For example N = 3; then it must dynamically add in my html code 3 times
<applicant-details></applicant-details>
Can I achieve this functionality with AngularJS with simple way? I tried several ways, and searched many times, but I couldn't find how to do this.
Even I tried to add that tags with JavaScript, but it understands only empty tags without content.
This is an example of simple looping in angular js
<div ng-app="myapp">
<div ng-controller="ctrlParent">
<ul>
<li ng-repeat="i in getNumber(myNumber) track by $index"><span>{{$index+1}}</span></li>
</ul>
<ul>
<li ng-repeat="i in getNumber(myOtherNumber) track by $index"><span>{{$index+1}}</span></li>
</ul>
</div>
var app = angular.module('myapp',[]);
app.controller('ctrlParent',function($scope){
$scope.myNumber = 5;
$scope.myOtherNumber = 10;
$scope.getNumber = function(num) {
return new Array(num);
}
});
check output:http://jsfiddle.net/sh0ber/cHQLH/

Angular ng-show is not showing the element in the view

I am trying to implement a simple Angular ng-show in my code but I cannot seem to make it work. I am attaching the code below.
HTML
<div ng-repeat="question in data.questions track by $index" ng-if="question.visible" class="slide">
<md-radio-group ng-model="question.selectedAnswer" ng-if="vm.showSliderChecker">
<md-radio-button ng-repeat="ans in question.answer track by $index" ng-value="ans._id"
ng-click="radioBtnClick({qId: question._id, ansId: ans._id})">{{::ans.description}}
</md-radio-button>
</md-radio-group>
<div class="wrapper" ng-show="vm.showSliderChecker">
<div class="toggle_radio">
<input type="radio" class="toggle_option" id="first_toggle" name="toggle_option" >
<label for="first_toggle" class="widthEighth" ng-repeat="ans in question.answer track by $index" ng-value="ans._id"
ng-click="radioBtnClick({qId: question._id, ansId: ans._id}); showSliderClickedMessage()"><p> {{$index+1}} </p></label>
</div>
</div>
</div>
Angular Controller
vm.checkSliderForDisplay = checkSliderForDisplay;
function initController(){
checkSliderForDisplay();
}
function checkSliderForDisplay() {
if($stateParams.testType === "Stress_management"){
vm.showSliderChecker = true;
console.log("The value of slider checker is true and here's proof ----> ");
console.log(vm.showSliderChecker);
}
else{
vm.showSliderChecker = false;
console.log("Alas, the slider checker is just a lot of false advertising and nothing more.");
console.log(vm.showSliderChecker);
}
}
initController();
Now here's the problem:
The console logs are showing on the console but the both the divs stay hidden. I have looked for a solution on this website and tried a bunch of combinations thinking I am missing some minute detail but I just can seem to make it work.
As you are using the Controller As synstax vm is the name you can refer to the controller in the scope.
If your code is running in a different closure, like from the controller constructor, you may need to refer to it differently.
Looks like that piece of code is called in the Controller constructor so "vm" won't be a defined variable there. You should use "this" instead:
function checkSliderForDisplay() {
if(this.testType === "Stress_management"){
this.showSliderChecker = true;
console.log("The value of slider checker is true and here's proof ----> ");
console.log(this.showSliderChecker);
}
else{
this.showSliderChecker = false;
console.log("Alas, the slider checker is just a lot of false advertising and nothing more.");
console.log(this.showSliderChecker);
}
}
If you add "use strict"; at the top of your js files you should get some warning.

Angularjs compile a directive inside ng-repeat with isolated scope

I have a directive in the form of a dropdown, pretty simple. The user can click a button to add as many as they need to in a ul, make their selections, and save it off. This is all inside of several ng-repeats.
I'm having trouble mastering the scope. As I expected, this works:
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li><new-case group='group'></new-case></li>
</ul>
</div>
</div>
When I say "works", I mean that group is properly scoped (the data of the entire group is necessary for the resulting input).
When I switch it to "click to add":
<div ng-repeat="group in groups" question-group="group" class="question-group">
<div ng-repeat="question in questions">
<ul>
<li ng-repeat="case in question.cases"></li>
<li>add case</li>
</ul>
</div>
</div>
group is undefined in the scope. Here is my createNewCase function:
function createNewCase($event) {
var thisLi = angular.element($event.target).closest('li');
var listItem = $compile('<li><new-case group=\'group\'></new-case></li>');
var html = listItem($scope);
thisLi.before(html);
}
$scope.createNewCase = createNewCase;
And the newCase directive:
angular.module('groups.directives.newCaseDirective', [])
.directive('newCase', ['$window', function() {
return {
restrict: 'EA',
scope: { group: '=' },
templateUrl: 'groups/views/newcase.tpl.html'
};
}]);
I've been reading for days and I've tried a few other derivatives but I'm ultimately just not getting it. Help is greatly appreciated.
Thanks!
The issue is that group is created by ng-repeat and is only available in child scopes of ng-repeat.
Each repeated element is in it's own child scope. So your directive version works but your other one doesn't because the controller doesn't see those child scopes.
You would have to pass group as argument of the function if you want to access it in controller
<a href="#" ng-click="createNewCase($event, group)">

Live search in AngularJS: updating the results

I want a live search: the results are queried from web api and updated as the user types.
The problem is that the list flickers and the "No results" text appears for a fraction of second, even if the list of results stays the same. I guess I need to remove and add items with special code to avoid this, calculating differences between arrays, etc.
Is there a simpler way to avoid this flicker at least, and probably to have possibility to animate the changes?
It looks like this now:
The html part is:
<div class="list-group">
<a ng-repeat="test in tests track by test.id | orderBy: '-id'" ng-href="#/test/{{test.id}}" class="list-group-item">
<h4 class="list-group-item-heading">{{test.name}}</h4>
{{test.description}}
</a>
</div>
<div ng-show="!tests.length" class="panel panel-danger">
<div class="panel-body">
No tests found.
</div>
<div class="panel-footer">Try a different search or clear the text to view new tests.</div>
</div>
And the controller:
testerControllers.controller('TestSearchListCtrl', ['$scope', 'TestSearch',
function($scope, TestSearch) {
$scope.tests = TestSearch.query();
$scope.$watch('search', function() {
$scope.tests = TestSearch.query({'q':$scope.search});
});
}]);
You should use ng-animate module to get the classes you need for smooth animation. For each ng-repeat item that's moved, added, or removed - angular will add specific classes. Then you can style those classes via CSS or JS so they don’t flicker.
An alternative way of doing what you require is to use the angular-ui bootstrap Typeahead component (check at the bottom of the post). It has a type-ahead-wait property in milliseconds and also a template url for customising it.
<div ng-app>
<div ng-controller="MyController">
<input type="search" ng-model="search" placeholder="Search...">
<button ng-click="fun()">search</button>
<ul>
<li ng-repeat="name in names">{{ name }}</li>
</ul>
<p>Tips: Try searching for <code>ann</code> or <code>lol</code>
</p>
</div>
</div>
function MyController($scope, $filter) {
$scope.names = [
'Lolita Dipietro',
'Annice Guernsey',
'Gerri Rall',
'Ginette Pinales',
'Lon Rondon',
'Jennine Marcos',
'Roxann Hooser',
'Brendon Loth',
'Ilda Bogdan',
'Jani Fan',
'Grace Soller',
'Everette Costantino',
'Andy Hume',
'Omar Davie',
'Jerrica Hillery',
'Charline Cogar',
'Melda Diorio',
'Rita Abbott',
'Setsuko Minger',
'Aretha Paige'];
$scope.fun = function () {
console.log($scope.search);
$scope.names = $filter('filter')($scope.names, $scope.search);
};
}

duplicates in a repeater are not allowed angular

I'm trying to create a form like below, this using ng-repeat directive in angular and it whenever I created a new row complains
"Duplicates in a repeater are not allowed.".
While I understand the solution for this is by putting "track by $index", however it causes another issue, which clicking delete on one row deletes the value of other field. So I suspect that track by index is OK for static text but not input form. So how to use ng-repeat correctly for my case?
My jsfiddle : demo.
Edit : I do aware that json array of object will solve my issue ( because for object angular create $$hashKey ) and already implemented this for most of my other module. But I am actually expecting some fix that can be done without really change my json array of string. Sorry for not being clear.
My current code :
HTML
<div class="row-fluid spacer10">
<a ng-click="addAKA()" class="btn btn-primary spacer5 left30"><i class="icon-plus icon-white"></i> Add New Alias</a>
</div>
<div class="row-fluid spacer10"></div>
<div class="row-fluid spacer5" ng-repeat="item in aliasList track by $index">
<input type="text" class="span6 left30" ng-model="item">
<button class="btn btn-danger" ng-click="deleteAKA($index)">delete</button>
<BR/>
</div>
Javascript
$scope.addAKA = function ()
{
if($scope.aliasList == null)
{
$scope.aliasList = [];
}
$scope.aliasList.push("");
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
$scope.deleteAKA = function (idx)
{
var aka_to_delete = $scope.aliasList[idx];
$scope.aliasList.splice(idx, 1);
$scope.aliasjson = JSON.stringify($scope.aliasList);
}
I would guess this is caused when there are more than one empty strings in the list.
If this is the case, it is caused because any two empty strings are equals in JS and Angular repeater does not allow duplicate values (as clearly stated in the message). This is a valid decision as they have to relate an object in the list with its DOM tree to minimize DOM manipulation.
A solution would be to insert simple objects containing the string in the model:
$scope.addAKA = function () {
...
$scope.aliasList.push({value:""});
...
};
And adjust your template:
<input type="text" class="span6 left30" ng-model="item.value">
Since all new objects are different, your problem should be solved.
See a fiddle where a filter is implemented to transform the model back to a list of strings.
When you type in a new created input, your list stays the same. Angular on any list change will update the view (ng-repeat) and remove all new stored text. Therefore we need to add ng-change to update our list on any input change
Add ng-change="change(i, $index) to your item and it should work
HTML
<div ng-controller='ctrl'>
<ol>
<li ng-repeat='i in list track by $index'>
<input type='text' ng-model='i' ng-change="change(i, $index)"></input>
<button ng-click='deleteItem($index)'>Delete</button>
</li>
</ol>
<button ng-click='addItem()'>Add</button>
<div>ITEM: {{list | json}}</div>
</div>
Javascript
angular.module("app", []).controller("ctrl", function ($scope) {
$scope.list = ["one","two"];
$scope.addItem = function ()
{
$scope.list.push("");
};
$scope.deleteItem = function (idx)
{
var item_to_delete = $scope.list[idx];
$scope.list.splice(idx, 1);
};
$scope.change = function (item, idx)
{
$scope.list[idx] = item;
};
});
See fixed Demo in DEMO
Yes, pushing more than one empty string will result in ng-repeat complaining.
In addition, you can also try:
if ($scope.aliasList.indexOf(VALUE_TO_ADD) === -1) {
...
}

Resources