I am working on a simple todo app that adds task to an existing array of json data. However when I try to add more than one task I get this error: Error: [ngRepeat:dupes]. I can't for the life of me figure out what is going wrong. I suspect it might have something to do with the namespace, but after changing names around a few times I still get the same error. If anybody could point me in the right direction I would much appreciate it.
The HTML Code
<div id="taskComplete" ng-app="taskComplete">
<div class="container" ng-controller="taskCtrl">
<div id="taskCompleteHeading" class="row">
<div class="col-xs-12">
<div class="page-header">
<h1 class="text-center">TaskComplete <small>An AgularJs App</small></h1>
</div>
</div>
</div>
<div id="newTaskSubmit">
<input type="text" ng-model="newTask.title">
<input type="text" ng-model="newTask.description">
<button type="button" ng-click="addTask(newTask)">Add Task</button>
</div>
<div class="well">
<pre>{{newTask | json}}</pre>
</div>
<div ng-repeat="task in activeTasks">
<h4>{{task.title}}</h4>
</div>
</div>
</div>
UPDATED SOLUTION
<div ng-repeat="task in activeTasks track by $index">
<h4>{{task.title}}</h4>
</div>
The JAVASCRIPT Code
angular
.module('taskComplete')
.controller('taskCtrl', function($scope, taskFactory) {
$scope.activeTasks;
taskFactory.getTasks().success(function(data) {
$scope.activeTasks = data;
console.log($scope.activeTasks);
}).error(function(error) {
console.log(error);
});
$scope.newTask = {};
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
}
});
Use this
<div ng-repeat="task in activeTasks track by $index">
<h4>{{task.title}}</h4>
</div>
So angular will track your ng-repeat node
Use track by $index:
ng-repeat="task in activeTasks track by $index"
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
}
Should become
$scope.addTask = function(newTask) {
$scope.activeTasks.push(newTask);
$scope.newTask = {};
}
The error was caused by the same object being used twice. It is also why changes below were being mirrored by the added task.
Related
I'm having trouble trying to figure out how to fix a bug that happens when I try to push the same string as added previously to the array. It gets stuck and will not allow the app to post another string.
How do I make sure that your repeat doesn't get stuck when there two of the same values in the array?
Bug Example Screenshot
--> Bug happens when I try to push "1"into the array again after a "1" is already posted.
HTML Code
<body>
<div data-ng-controller="appController" class="container">
<div class="row">
<form>
<label for = "status"> Status: </label>
<input data-ng-model = "input_data" type="text" id="status"/>
<button data-ng-click="add_data()"> OK </button>
<ul class = "list-group">
<li class="list-group-item" data-ng-repeat="x in array_data">
{{x}}
<button data-ng-click = "remove_data($index)">DEL</button>
</li>
</ul>
</form>
</div>
</div>
<script src="framework/js/jquery.min.js"></script>
<!-- All Bootstrap plug-ins file -->
<script src="framework/js/bootstrap.min.js"></script>
<!-- Basic AngularJS -->
<script src="framework/js/angular.min.js"></script>
<!-- Your Controller -->
<script src="framework/js/appstatpost.js"></script>
</body>
AngularJS code
var app = angular.module("myApp", []);
app.controller("appController", function ($scope) {
$scope.array_data = [];
$scope.add_data = function () {
$scope.array_data.push($scope.input_data);
};
$scope.remove_data = function (index) {
$scope.array_data.splice(index, 1);
};
});
You could use track by $index, example:
<li class="list-group-item" data-ng-repeat="x in array_data track by $index">
AngularJS tries by default to find a key in your array to index by. Normally this works well, but if you have duplicates then you have to tell AngularJS to make a new index, in this case, $index.
I am making a simple sports goods shopping app in AngularJs.
I am in a situation where I have three nested ng-repeats.
First loop: Get the brand name. I have written angularjs service that calls the rest endpoint to fetch the lists of brands (Adidas, Yonex, Stiga, etc). I am calling this service as soon as the page(controller) gets loaded.
Second loop: For each brand, I want to display the category of products they are offering. Inside this loop, I want to execute a function/service that will take the brand name as input and get all the categories for the brand. For this, I also have an angularjs service that calls the rest endpoint to fetch the list of categories for a given brand name.
Third loop: For each brand and category, I want to display the products in that category. Inside this loop, I want to execute a function that will take the brand name and category as input and get all the products in that category. I an angularjs service call which will call the rest endpoint to fetch the products given the brand name and category.
Sample data set:
Adidas
-----T-Shirts
----------V-Neck
----------RoundNeck
-----Shoes
----------Sports Shoes
----------LifeStyle Shoes
Yonex
-----Badminton Racquet
----------Cabonex
----------Nanospeed
-----Shuttlecocks
----------Plastic
----------Feather
Stiga
-----Paddle
----------Procarbon
----------Semi-carbon
-----Ping Pong Balls
----------Light Weight
----------Heavy Weight
Please note that because of some constraints I cannot have a domain object on the REST side to mimic the data structure shown above.
I want to display the above data in a tree-like fashion (something on the same lines as shown above possibly with expand/collapse options).
Below are the code snippets.
CONTROLLER:
(function () {
'use strict';
angular.module('SportsShoppingApp.controllers').controller('sportsController', ['sportsService', '$scope', function (sportsService, $scope) {
$scope.brands = [];
$scope.categories = [];
$scope.products = {};
$scope.getBrands = function () {
sportsService.getBrands()
.then(loadBrands, serviceError);
};
var loadBrands = function(response) {
$scope.brands= response.data;
};
$scope.getCategories = function(brand) {
sportsService.getCategories(brand)
.then(loadCategories, serviceError);
};
var loadCategories = function (response) {
$scope.categories = response.data;
};
$scope.getProducts = function(brand, category) {
sportsService.getProducts(brand, category)
.then(loadProducts, serviceError);
};
var loadProducts = function (response) {
$scope.products = response.data;
};
var serviceError = function (errorMsg) {
console.log(errorMsg);
};
$scope.getBrands();
}]);
}());
HTML:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-repeat="category in categories.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
When I use the above HTML, only the brand names are displayed on the UI. The categories and their corresponding products are not displayed. I know that there is some overlapping that is happening. I am not sure if I am doing it the right way. I might be completely wrong with my approach. I am new to AngularJS. I want to know how to loop in nested ng-repeat so that each ng-repeat could call an angularjs service and also I want to display the data in the tree fashion as shown above. Can someone help me here?
I think that the ng-inits have to be placed on separate tags to the ng-repeats:
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<div ng-repeat="brand in brands.data">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<div ng-init="getCategories(brand)">
<div ng-repeat="category in categories.data">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-init="getProducts(brand, category)">
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
You might have to juggle your bootstrap classes around also, moving ng-init is only to fix the angular part.
Move the ng-init directives outside of the ng-repeat to which they provide data.
<div class="well">
<div class="row">
<div id="sportsHeader" class="col-md-3">
<!-- MOVE init of categories here -->
<div ng-repeat="brand in brands.data" ng-init="getCategories(brand)">
<div class="row">
<div class="col-md-9">{{brand}}</div>
</div>
<!-- MOVE init of products here -->
<div ng-repeat="category in categories.data" ng-init="getProducts(brand, category)">
<div class="row">
<div class="col-md-9">{{category}}</div>
</div>
<div ng-repeat="product in products.data">
<div class="row">
<div class="col-md-9">{{product}}</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
The ng-init directive has a priority of 450; the ng-repeat, priority 1000. This means that when they are on the same element ng-init executes after the ng-repeat directive. The ng-repeat for categories.data won't execute its ng-init until it has a category. Thus its ng-init can't be used to populate the categories array.
Quick question. Is my approach correct ?
The approach works but it violates the Zen of Angular and the principles of an MV* Model View Whatever framework.
The model is the Single Source of Truth
Because the view is just a projection of the model, the controller is completely separated from the view and unaware of it. This makes testing a snap because it is easy to test your controller in isolation without the view and the related DOM/browser dependency.
--AngularJS Developer Guide -- Data-Binding
Having the ng-repeat and ng-init directives build the model creates a dependency that makes testing and debugging difficult. (As witnessed by this question.)
My advice is to learn how to build the model by chaining promises and using $q.all.
i am having a problem with angularjs and $index
i give it at a parameter to a function. It works at HTML in {{}} ({{$index}} works). But it doesnt work in the function. I alert the index and its always 0...
I used it at another position in my project and it works fine..
Here is the code.
<input type="checkbox" id="rounded1" ng-click="setClickEvent($index)" ng-model="clickStatus"/>
JS File with the function:
$scope.setClickEvent = function(index) {
alert(index);
};
the alert is always 0....
i hope someone could help me. Thanks :)
It looks like you're not using this <input> tag in an ng-repeat directive. $index will show 0 unless it's used in an ng-repeat where it will count from 0 to n.
Perhaps you meant to do something like this:
<div ng-repeat="i in [1, 2, 3, 4, 5] track by $index">
<input type="checkbox" ng-click="setClickEvent($index)" ng-model="clickStatus"/>
</div>
I removed the ID because each of the 5 checkboxes would have the same ID which isn't valid.
Sorry, here is the whole html code with the ng-repeat
<div ng-repeat="frage in frageListe">
<div class="input-group">
<div class="container-fluid list-group-item ">
<div class="rounded">
<input type="checkbox" id="rounded1" ng-click="setClickEvent(frage.id)" ng-model="clickStatus"/>
<label for="rounded1"></label>
</div>
<div class="list-text">
<h3 class="list-group-item-heading quest-list-text serif">{{frage.titel}} {{$index}}</h3>
</div>
</div>
</div>
<hr class="divider-line-questlist">
</div>
Not sure if this is relevant, but I had the same problem. For me the issue was that I was nesting multiple ng-repeats. To solve this I set aliases for the indexes with ng-init.
https://docs.angularjs.org/api/ng/directive/ngInit
<script>
angular.module('initExample', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.list = [['a', 'b'], ['c', 'd']];
}]);
</script>
<div ng-controller="ExampleController">
<div ng-repeat="innerList in list" ng-init="outerIndex = $index">
<div ng-repeat="value in innerList" ng-init="innerIndex = $index">
<span class="example-init">list[ {{outerIndex}} ][ {{innerIndex}} ] = {{value}};</span>
</div>
</div>
</div>
.controller('PizzaCtrl', ['$scope','$state','$ionicLoading',
function($scope, $state, $ionicLoading) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$scope.items = $scope.$parent.menu.pizze;
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
The data all checks out. The correct item information is logged to the console. However, the ng-repeat="item in items" directive in my view does not update with the pizza items.
I tried using $scope.$apply and $scope.$digest inside the event listener, but the console threw an error saying the digest was already in progress.
Also worth noting that this controller has two sibling controllers that have identical logic to this one, except for different sections of the menu. The console.log("pizza") statement isn't executed until I click into the state.
Is there a clear reason why my view is not updating?
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()">
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>
Instead of using $scope.$apply try to use $timeout angular service.
The $timeout does not generate error like „$digest already in progress“ because $timeout tells Angular that after the current cycle, there is a timeout waiting and this way it ensures that there will not any collisions between digest cycles and thus output of $timeout will execute on a new $digest cycle.
.controller('PizzaCtrl', ['$scope','$state','$ionicLoading', '$timeout'
function($scope, $state, $ionicLoading, $timeout) {
$scope.$emit('menu-refresh-request');
$scope.$on('menu-refresh-response', function(event) {
console.log("pizza");
$scope.$broadcast('scroll.refreshComplete');
$timeout(function(){
$scope.items = $scope.$parent.menu.pizze;
});
console.log($scope.items[1].price);
$ionicLoading.hide();
});
$scope.doRefresh = function() {
$scope.$emit('menu-refresh-request');
};
}])
Turns out the solution to this problem is that I needed to add a missing closing tag to the <ion-refresher> tag.
<ion-refresher pulling-text="Updating Menu..." on-refresh="doRefresh()"></ion-refresher>
<div class="list menu-list">
<a class="item menu-item" ng-repeat="item in items" ui-sref="menu.pizza-detail({ index: $index })">
<div class="row">
<h3 class="row" ng-bind="item.name"></h3>
<div class="row">
<div class="list-price col col-15">
<h4 class="list-value" ng-bind="item.price"></h4>
</div>
<div class="list-description col col-85">
<p ng-bind="item.description"></p>
</div>
</div>
</div>
</a>
</div>
There are quite a few questions on how to implement item removal inside ngRepeat directive, and as I figured out, it comes down to using ngClick and triggering some remove function passing it item's $index.
However, I couldn't find anywhere an example where I have multiple ngRepeats:
<div ng-controller="MyController">
<div ng-repeat="email in user.emails">
{{ email }} <a href>Remove</a>
</div>
<div ng-repeat="phone in user.phones">
{{ phone }} <a href>Remove</a>
</div>
</div>
For this, I would need to create $scope.removePhone and $scope.removeEmail which would be called using ngClick on Remove anchor. But I'm looking for a more generic solution. Especially since I have many pages with many ngRepeats .
I was thinking about writing a directive which would be placed on Remove anchor and would do something like this:
Find ngRepeat among parent elements.
Read what it's iterating over ('user.emails' in first case, 'user.phones' in second)
Remove $index element from THAT model.
So the markup would look something like this:
<div ng-controller="MyController">
<div ng-repeat="email in user.emails">
{{ email }} <a href remove-directive="$index">Remove</a>
</div>
<div ng-repeat="phone in user.phones">
{{ phone }} <a href remove-directive="$index">Remove</a>
</div>
</div>
Is what I'm looking for possible to achieve and what would be the preferred way to do this?
Current hacky solution
Here is how I do it currently. It's hacky and ugly but gets the job done until I figure out a prettier way.
myAppModule.controller('MyController', function ($scope, $parse, $routeParams, User) {
$scope.user = User.get({id: $routeParams.id});
$scope.remove = function ($index, $event) {
// TODO: Find a way to make a directive that does this. This is ugly. And probably very wrong.
var repeatExpr = $($event.currentTarget).closest('[ng-repeat]').attr('ng-repeat');
var modelPath = $parse(repeatExpr.split('in')[1].replace(/^\s+|\s+$/g, ''));
$scope.$eval(modelPath).splice($index, 1);
};
});
And in DOM:
<div ng-repeat="email in user.email" class="control-group">
<label class="control-label">
{{ "Email Address"|_trans }}
</label>
<div class="controls">
<input type="text" ng-model="email.address">
<span class="help-inline"><a href ng-click="remove($index, $event)">{{ "Delete"|_trans }}</a></span>
</div>
</div>
You could create a generic remove method that would take in the array and the item to remove.
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }} <a ng-click="remove(emails, $index)">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }} <a ng-click="remove(phones, $index)">Remove</a>
</div>
</div>
$scope.remove = function(array, index){
array.splice(index, 1);
}
No JS
<div ng-repeat="option in options" ng-init=options=[1,2,3,4,5]>
<button ng-click="options.splice($index,1)">Remove me</button>
</div>
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails as datasource">{{ email }}
<a ng-click="datasource.splice($index,1)">Remove</a>
</div>
<div ng-repeat="phone in phones as datasource">{{ phone }}
<a ng-click="datasource.splice($index,1)">Remove</a>
</div>
</div>
A very simple and convenient way that works cross-browser is to use the 'remove' utility method from the library lodash.
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="removeItem(phones, phone)">Remove</a>
</div>
In your controller you declare then
//inject lodash dependency
//declare method in scope
$scope.removeItem = function(list, item){
lodash.remove(list,function(someItem) { return item === someItem});
}
You may of course use indexes if you like. See https://lodash.com/docs#remove
If you have used ng-repeat on an object instead of an array, do the following.
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }}
<a ng-click="remove(emails, email)">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="remove(phones, phone)">Remove</a>
</div>
</div>
$scope.remove = function(objects, o){
delete object[o.id];
}
or the more terse
<div ng-app="" ng-controller="MyController">
<div ng-repeat="email in emails">{{ email }}
<a ng-click="delete emails[email.id]">Remove</a>
</div>
<div ng-repeat="phone in phones">{{ phone }}
<a ng-click="delete phones[phone.id]">Remove</a>
</div>
</div>
presumes that the objects look like this
var emails = { '123' : { id : '123', .... } };
var phones = { '123' : { id : '123', .... } };