AngularJS - calculate value for each field - angularjs

I have list of fields(todos) with button on their left. I want to click on one of the buttons so it calculate the value for this specific field.
What I'm getting is that clicking on specific button calculate and show the same result for all the fields
this is what I wrote :
<h1 ng-show="myVar">
<ul>
<li ng-repeat="todo in todos">
<button ng-show="!editing[$index]" ng-click="edit($index)">click</button>
<h2> Result is:{{result}}</h2>
{{todo.name}}
</li>
</ul>
</h1>
and the controller
$scope.edit = function(index){
var todo = $scope.todos[index];
var counter = 0;
angular.forEach($scope.todos,function(value,index){
if (todo.topic == 'important')
{counter = counter+1;}
})
$scope.result = counter;
}
What am i doing wrong?

Basically result variable of scope you have used for binding is not the same scope variable which has been defined inside controller scope.
Because ng-repeat does work in quite a different way, when it render a DOM by looping through provided collection(here its todos), it creates a new scope for each iteration which is prototypically inherited from it parent scope.
Do use Dot Rule while defining model, so that it would follow prototypal inheritance,
$scope.model = {};
//then use
$scope.model.result = result;
HTML
<h2> Result is:{{model.result}}</h2>
Other way around to sort this issue out would be using controllerAs approach while defining controller, but in that case you need to get read of $scope & should replace it with this context of controller function.

You use the same index variable name as a function input parameter - function(index) and in forEach loop ,function(value,index){ - and it gets overwritten.
//here
$scope.edit = function(index){
var todo = $scope.todos[index];
var counter = 0;
//and here
angular.forEach($scope.todos,function(value,index){

Related

Add new entry to (empty) scope array using ng-click directive

My scope does not contain an "notYetExistingArray" at the time of generation.
Via a button, I would like to add the array plus a first entry to it.
Each subsequent push of the same button should then add another entry.
Unfortunately, I have no clue on how to approach it.
Currently I have:
<button type="button" ng-click="notYetExistingArray.unshift({})">
If the scope already contains an object "notYetExistingArray" of type array = [] I can easily add an entry with above function.
Any advise on how to do it?
Call a controller function from your ng-click directive rather than trying to do all that stuff in your markup. It can include a check for the existence of the array, and create it if needed.
<button type="button" ng-click="addThis(thing)">
In the controller:
ctrl.addThis = function(thing) {
if (ctrl.myArray === undefined) {
ctrl.myArray = [];
}
myArray.unshift(thing);
};
Note that I'm using controllerAs syntax here, so ctrl might be $scope instead.
You can initialize the array before doing that, for example:
<button type="button" ng-init="notYetExistingArray=[]" ng-click="notYetExistingArray.unshift({})">
check the documentation for ng-init https://docs.angularjs.org/api/ng/directive/ngInit
A good way is to bind a function on the ngClick directive, which will perform a test in order to check if the array exist.
In your controller :
function foo(item) {
//if my array does not exist
if (!$scope.myArray) {
//create the array
$scope.myArray = [];
}
//Here, we can add some data to our array, we are sure that the array exist
$scope.myArray.unshift(item);
}
$scope.foo = foo;
Html :
<h2>Array.length : {{myArray.length}}</h2>
</br>
<button type="button" ng-click="foo({})">Add</button>
Feel free to check the Working Plunker

accessing controller $scope in filter not defined on controller

Inside an ng-repeat, I wish to format the date on the fly as the user has the option to change the formatting, so I created a filter like this
<ul>
<li ng-repeat"item in items">
<h2>{{item.date | formatDate }} </h2>
</li>
</ul>
And then in my code
myapp.filter('formatDate', function(){
return function(date) {
return formatDateFunction(date)
}
}
function formatDateFunction(unix_epoch){
var date = new Date(unix_epoch);
var language = $scope.desiredLanguage
var time = date.toLocaleTimeString(language, options);
return time;
}
myapp.controller('MyCtrl',[$scope,
function MyCtrl($scope){
$scope.desiredLanguage = 'en-us';
--code omitted
The problem is that the filter calls a function that requires a value defined on the $scope of the controller so the way that I've currently written the code that value is not available i.e. $scope is not available in the format filter, nor the formatDateFunction that it calls.
Question: how can I access the scope in the filter above, or, conversely, arrange it so that <h2>{{item.date | formatDate }} </h2> calls a filter on the controller?
You've mixed your separation of concerns, you should be providing the desired language as a parameter to your filter. This means that you would still define desiredLanguage in the controller as a scope variable a la
$scope.desiredLanguage = 'en-us'
but you would pass it to your filter as a parameter through the HTML. This means that your controller stays the same, and your filter becomes:
myapp.filter('formatDate', function(){
return function(date,language) {
return formatDateFunction(date,language)
}
}
function formatDateFunction(unix_epoch, language){
var date = new Date(unix_epoch);
var time = date.toLocaleTimeString(language, options);
return time;
}
Then in your html you can just use:
<h2>{{item.date | formatDate:desiredLanguage }} </h2>
This keeps the separation of scope and parameters in your control, which encourages reuse, modularity, and testability.

How to look up items with id for one controller from another controller in AngularJS

I have two controllers where the first one contains a list of items:
$scope.items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
The second one binds to a template which displays a list of items selected:
$scope.myitems = [1,2,3,...]; // ids only
<div ng-repeat="item in myitems">
{{item.id}} / {{item.desc}}
</div>
How can I look up the item desc in the ng-repeat of the second controller from the item list of the first controller?
Sharing data between controllers is best achieved via services/factories, an alternative is to use scope inheritance.
1. Factory/Service
angular.module('yourModule').factory('itemService', itemService);
function itemService() {
var items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
return {
findItemById: findItemById
}
function findItemById(id) {
//logic to find item by id
}
}
Inject this factory in your controllers and add more functions if needed.
2. Scope inheritance
The key here is to nest your childcontroller, which I presume is the one with the ids.
<div ng-controller="topCtrl">
<div ng-controller="childCtrl">
<div ng-repeat="item in myitems">
{{item.id}} / {{item.desc}}
</div>
</div>
</div>
With this option, any controller that is nested within the topCtrl in the view has access to the topCtrl scoped variables.
A third option would be to store the data in the $rootScope, which is actually also a sort of scope inheritance (all scopes except for isolated directive scopes inherit from the rootScope), but that's probably not a good idea for your usecase.
You can try to use inheritance concept:
var app = angular.module(....);
app.controller('FirstCtrl ', function($scope) {
$scope.items = [{id:1, desc:'desc1'},{id:2, desc:'desc2'}...];
});
app.controller('SecondCtrl', function($scope, $controller) {
$controller('FirstCtrl', {$scope: $scope}); // $scope.items now available
});
Advantage over the $rootScope solution:
second controller will always have access to items, even if first controller is not instantiated
items array may be changed only by these two controllers, and not anyone other
Main flaw of this approach is that SecondCtrl will have access to any scope variable of the FirstCtrl and not only items array
EDIT. IMHO Factory/Service approach mentioned by #NexusDuck is the best one (composition over inheritance).
Can you assign values to $rootScope.items in first controller, then try to access in second controller.
You can use $emit and $on.
EX:
In First controller add
$scope.$emit('eventName', $scope.items);
};
And also pass $scope.items.
In Second Controller Add
$scope.$on('eventName', function (event, args) {
$scope.items = args;
});
args hold $scope.items value and is assign to $scope.items in second controlle, so now ng-repeat display value of $scope.items of First Controller.

What's the difference between using function and using inline expression to set scope variable

I found some differences between executing function and using expression to set variable, specifically, it seems that ng-if fails to detect the changes made by expression. I don't understand why.
Pseudo-code:
if($scope.editingProfile)
display edit section
click(backToList button)
hide edit section
The backToList button has a ng-click attribute, when I write ng-click="backToList()" to execute $scope.backToList() in which the $scope.editingProfile is set to false it works good. But when I write ng-click="editingProfile=false" to set the variable directly, the ng-if used to hide the section won't work.
fiddle: http://jsfiddle.net/brfbtbd7/1/
It is because ng-if directive creates a child scope. So ng-if="editingProfile" is the editing profile object on the parent which gets inherited to the child scope created by the ng-if. That is what gets displayed # Editing {{editingProfile.name}}<br>. When you do ng-click="editingProfile=false" you are actually updating the child scope's inherited version of editingProfile which does not gets reflected in the one at the parent. But when you do ng-click="backToList()" The function is in the controller and so the $scope.editingProfile refers to the one on the controller (parent) hence that change is reflected and ng-if becomes falsy and it hides the content.
You could solve this my adding one more level in the scope property hierarchy.
angular.module("testApp", []).controller("editCtrl",
function ($scope){
$scope.model = {};
$scope.model.editingProfile = null;
$scope.edit = function() {
$scope.model.editingProfile = {
name: "test"
};
}
$scope.backToList = function() {
$scope.model.editingProfile = null;
}
}
)
and
<div ng-if="model.editingProfile">Editing {{model.editingProfile.name}}
<br> <a ng-click="backToList()">click to execute function to go back</a>
<br/> <a ng-click="model.editingProfile=null">click to set variable to go back(NOT working)</a>
</div>
Fiddle

AngularFire - Remove Single Item

Here is the relevant code in my view:
p(ng-repeat="t in todos")
input(
type="checkbox",
ng-model="t.done",
ng-click="clearItem($event)"
)
{{t.text}} done? {{t.done}}
When the checkbox is clicked, I want the appropriate object in the todos array to be removed from the database.
My clearItem function is as follows:
$scope.clearItem = function(event) {
todoRef.remove($scope.t);
}
However, this removes all the entries in my database. I want it to remove only the specific object in question. Is there anyway for me to do this?
Ok, figured it out.
When looping using ng-repeat, use (id, t) in todos. This allows you to send id as the parameter to the ng-click function, and $scope.todos.$remove(id) works just fine.
To provide a more complete example for anyone else that lands here, according to Firebase's documentation for AngularFire this would be the preferred way, and I believe the easiest way to remove an object:
// Create an app. Synch a Firebase array inside a controller
var myApp = angular.module("myApp", ["firebase"]);
// inject $firebaseArray
myApp.controller("TodoCtrl", ["$scope", "$firebaseArray", function($scope, $firebaseArray) {
// bind $scope.todos to Firebase database
$scope.todos = $firebaseArray(myFirebaseRef.child("todo"));
// create a destroy function
$scope.removeTodo = function(todo) {
$scope.todos.$remove(todo);
};
}]);
In your view, you could do something like below. Note that you could bind the removeTodo function to a checkbox as the question specifies, or a regular old <a href> element:
// In your view
<div ng-controller="TodoCtrl">
<ul>
<li ng-repeat="todo in todos">
{{ todo.text }} : <a href ng-click="removeTodo(todo)">X</a>
</li>
</ul>
</div>
Hope that helps!
A better solution would be to have $scope.clearItem() take the object t as an argument, instead of $event.
HTML - <p ng-repeat="t in todos"><input... ng-click="clearItem(t)">
JS - $scope.clearItem = function(obj) {todoRef.$remove(obj)};
The only way I'm able to remove the item is using a loop on the array we get from firebase.
var ref= new Firebase('https://Yourapp.firebaseio.com/YourObjectName');
var arr_ref=$firebaseArray(ref);
for(var i=0;i<arr_ref.length;i++){
if(key==arr_ref[i].$id){
console.log(arr_ref[i]);
arr_ref.$remove(i);
}
}
The easiest way to remove the object would be
scope.clearItem = function(event) {
todoRef.$loaded().then(function(){
todoRef.$remove($scope.t)
});
The asynchronous nature of the beast has gotten me a few times.

Resources