reactjs - changing state doesn't re-render component - reactjs

I'm using a map function to create a game board from an array of objects and calling setState on click to make the game happen. I can successfuly update the state but the view won't update until I perform a different action. I'm guessing the problem is in how the map function passes props to the child element (Cell), but I can't figure out what I'm doing wrong.
var board = [];
var width = 50;
var height = 30;
var size = width * height;
for (var i=1; i<=size; i++) {
board[i] = {id: i, status: 'dead'};
}
var Cell = React.createClass({
turn: function() {
this.props.turn(this.props.id);
},
render: function() {
return <div id={this.props.id} className={this.props.status} onClick={this.turn}></div>
}
});
var GameBoard = React.createClass({
getInitialState: function() {
return {
board: board
};
},
handleClick: function(id) {
var newBoard = this.state.board;
newBoard[id] = {id: id, status: 'alive'};
this.setState({board: newBoard});
},
move: function() {
var board = this.state.board;
var newBoard = board;
for (var j=1;j<=board.length;j++) {
if (board[j].status == 'alive') {
newBoard[j-1] = {id: j-1, status: 'alive'};
newBoard[j] = {id: j, status: 'dead'};
}
}
this.setState({board: newBoard});
},
render: function() {
var squares = this.state.board.map(function(item){
return <Cell id={item.id} status={item.status} turn={this.handleClick}/>
}.bind(this));
return (
<div>
<h1>Game of Life</h1>
<button className="btn btn-default" onClick={this.move}>Run</button>
<div className='boardContainer'>{squares}</div>
</div>
);
}
});
ReactDOM.render(<GameBoard/>,document.getElementById('app'));
http://codepen.io/Theeeus/pen/YpQzPO?editors=0010

The loop in your move function is off-by-one, so it throws an error before reaching the line with setState. The first two lines of the method simply assign a reference to this.state.board in the variable newBoard. Therefore, you're "updating" your state, but not correctly with setState. That's why the board updates on your next click, which calls setState in handleClick.
1) You should modify a deep copy of the state instead of the state itself
2) Fix the off-by-one-error in the for loop
var board = this.state.board;
var newBoard = board; // Note, this is a reference to this.state.board. That's bad!
for (var j=1; j<=board.length - 1; j++) { // note, -1
...
}

You're mutating this.state. The React docs specify that this is a no-no. Inside the GameBoard component, I'd rewrite your handleClick() and move() functions to look more like this:
handleClick: function(id) {
this.setState({
...this.state,
board: [
...this.state.board.slice(0, id),
{
id,
status: 'alive'
},
...this.state.board.slice(id + 1)
]
});
},
move: function() {
this.setState({
...this.state,
board: this.state.board.map((place, id, board) => {
const nextPlace = board[id + 1];
if (nextPlace.status === 'alive') {
return {
...place,
status: 'alive'
};
} else if (place.status === 'alive') {
return {
...place,
status: 'dead'
};
}
return place;
})
});
},
Note: these functions use some new Javascript features like the spread operator (...place) and arrow functions. It's possible that these features might result in syntax errors when you try to run this code.

please use
if(var i = 0;i < arr.length;i++)
,instead of
if(var i = 1;i<=arr.length;i++){}
In your question,when your j in your move function equals to board.length, it will make an error.
Change your two if cycle in your code .
I hope that I can help.
var board = [];
var width = 2;
var height = 3;
var size = width * height;
for (var i=0; i < size; i++) {
board[i] = {id: i, status: 'dead'};
}
var Cell = React.createClass({
turn: function() {
this.props.turn(this.props.id);
},
render: function() {
return <div id={this.props.id} className={this.props.status} onClick={this.turn}></div>
}
});
var GameBoard = React.createClass({
getInitialState: function() {
return {
board: board
};
},
handleClick: function(id) {
var newBoard = this.state.board;
newBoard[id] = {id: id, status: 'alive'};
this.setState({board: newBoard});
},
move: function() {
var board = this.state.board;
var newBoard = board;
for (var j=0;j < board.length;j++) {
if (board[j].status == 'alive') {
newBoard[j-1] = {id: j-1, status: 'alive'};
newBoard[j] = {id: j, status: 'dead'};
}
}
this.setState({board: newBoard});
},
render: function() {
var squares = this.state.board.map(function(item){
return <Cell key = {item.id} fuck={item.id} id={item.id} status={item.status} turn={this.handleClick}/>
}.bind(this));
return (
<div>
<h1>Game of Life</h1>
<button className="btn btn-default" onClick={this.move}>Run</button>
<div className='boardContainer'>{squares}</div>
</div>
);
}
});
ReactDOM.render(<GameBoard/>,document.getElementById('app'));
.dead{
width:10px;height:10px;border:1px solid red;margin:3px;
}
.alive{
border:1px solid green;margin:3px;width:10px;height:10px;
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
<div id="app"></div>

Related

ctr always undefined in function <reactjs>

<script type="text/jsx">
var Counter = React.createClass({
ChangeValue: function(ctr){
alert(this.state.arr[ctr]);
this.setState({
arr : this.state.arr[5]
});
},
getInitialState: function(){
return {
arr:[
'0','0','0',
'0','0','0',
'0','0','0'
],
arr2:[
0,0,0,
0,0,0,
0,0,0]
}
},
render: function(){
var sese = [];
var ctr = 0;
sese.push( <h1>Count: {this.state.arr}</h1>)
for(var i = 0; i < 3 ; i++)
{
for(var j = 0 ; j< 3 ; j++)
{
sese.push(<button type="button" key={ctr} id={ctr} onClick={this.ChangeValue.bind(null, this.ctr)}>{this.state.arr2[ctr]}</button>)
ctr = ctr + 1;
}
sese.push(<br/>)
}
return (
<div>{sese}</div>
);
}
});
ReactDOM.render(
<Counter/>,
document.getElementById('root')
);
</script>
i tried this
var Counter = React.createClass({
ChangeValue: function(ctr){
alert(this.state.arr[this.ctr]);
this.setState({
arr : this.state.arr[5]
});
},
and still undefined..
i'm studying react , get stucked with my ctr in alert , and don't know the solution..
i'm trying to change the value of my button. if i click the button, the value will become 1 but my ctr is always undefined. does anyone know why my alert is always undefined?
In your onClick you have to pass just ctr and not this.ctr as it does not exist.
onClick={this.ChangeValue.bind(null, ctr)}

How to split my JSON object in angularJS

I am trying to create pagination for my table that lists the objects returned from my DB as an object. My data structure will look something like:
$scope.myJSONObj = {
app1: {
id: 1,
appName: "appIntegrated1",
status: "Pending"
},
app2: {
id: 2,
appName: "appIntegrated2",
status: "Pending"
},
app3: {
id: 3,
appName: "appIntegrated3",
status: "Completed"
},
app4: {
id: 4,
appName: "appIntegrated4",
status: "Pending"
},
app5: {
id: 5,
appName: "appIntegrated5",
status: "Pending"
},
app6: {
id: 6,
appName: "appIntegrated6",
status: "Pending"
},
app7: {
id: 7,
appName: "appIntegrated7",
status: "Pending"
},
app8: {
id: 8,
appName: "appIntegrated8",
status: "Pending"
},
app9: {
id: 9,
appName: "appIntegrated9",
status: "Pending"
},
app10: {
id: 10,
appName: "appIntegrated10",
status: "Pending"
}
I am trying to split my structure in half, and display the first five results. I have a prev/next button, and when I click next, it should display the next 5 results (in this case the last 5). However, for everything to work, I need to be able to split my object, and so far every method I've researched involves arrays, and objects requiring some hack. I was wondering if I was missing something, or I have to create a solution to work with?
In pure JavaScript :
function getEntries(from, to) {
var entries = [];
for(var key in myJSONObj) {
// extract index after `app`
// var index = key.substring(3);
// Better way : extract index using regular expression, so it will match `something1`, `foo2`, `dummy3`
var index = parseInt(key.replace( /^\D+/g, ''));
if(index >= from && index <= to) {
entries.push(myJSONObj[key]);
}
}
return entries;
}
console.log(getEntries(0, 5));
Try _.chunk
https://lodash.com/docs/4.17.4#chunk
$scope.pages = _.chunk($scope.myJSONObj,5);
$scope.getPage = function( pageIndex ){
return $scope.pages[pageIndex];
}
It's untested - but I wrote a chunk method for you in vanilla JS since you can't use lodash.
function chunk(obj, chunkSize) {
var resultArray = [];
var resultArrayCurrentIndex = 0;
for (var key in obj) {
var item = obj[key];
if (resultArray[resultArrayCurrentIndex].length <= chunkSize) {
if (!resultArray[resultArrayCurrentIndex]) {
resultArray[resultArrayCurrentIndex] = [item];
} else {
resultArray[resultArrayCurrentIndex].push(item)
}
} else {
resultArrayCurrentIndex++
resultArray[resultArrayCurrentIndex] = [item];
}
}
return resultArray;
}
Then you can access it like this:
$scope.pages = chunk(yourObject, 5);
$scope.getPage = function(index){
return $scope.pages[index];
}
EDIT - changed it to accept an obj.
Used Object.keys, Array.prototype.slice and Array.prototype.reduce to solve your issue. Hope this helps
angular.module('app',[])
.controller('TestCtrl', function($scope){
$scope.myJSONObj = {"app1":{"id":1,"appName":"appIntegrated1","status":"Pending"},"app2":{"id":2,"appName":"appIntegrated2","status":"Pending"},"app3":{"id":3,"appName":"appIntegrated3","status":"Completed"},"app4":{"id":4,"appName":"appIntegrated4","status":"Pending"},"app5":{"id":5,"appName":"appIntegrated5","status":"Pending"},"app6":{"id":6,"appName":"appIntegrated6","status":"Pending"},"app7":{"id":7,"appName":"appIntegrated7","status":"Pending"},"app8":{"id":8,"appName":"appIntegrated8","status":"Pending"},"app9":{"id":9,"appName":"appIntegrated9","status":"Pending"},"app10":{"id":10,"appName":"appIntegrated10","status":"Pending"}};
$scope.currentPage = 0;
$scope.pageSize = 5;
$scope.totalPage = Math.ceil( Object.keys($scope.myJSONObj).length/$scope.pageSize);
//pageNumber starts from 0 here
$scope.goToPage = function(pageNumber) {
pageNumber = pageNumber>=0?pageNumber:0;
var from = pageNumber*$scope.pageSize;
var to = from + $scope.pageSize;
return Object.keys($scope.myJSONObj).slice(from,to).reduce(function(a,b){
a[b] = $scope.myJSONObj[b];
return a;
},{});
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="app" ng-controller="TestCtrl">
<button ng-disabled="currentPage===0" ng-click="currentPage = currentPage - 1">prev</button>
<button ng-disabled="currentPage===totalPage-1" ng-click="currentPage = currentPage + 1">next</button>
<b>Page: {{currentPage+1}}/{{totalPage}}</b>
<pre>{{goToPage(currentPage) | json}}</pre>
</div>

Dynamic angular chart

I'm trying to create a dynamic chart from userTemplate object.
I'm using this directive angular-flot and I want create the dataset and options of directive dynamically.
Its work but I have this error
Error: [$rootScope:infdig] http://errors.angularjs.org/1.2.21/$rootScope/infdig?p0=10&p1=%5B%5B%22fn%3…ection%5C%22%3A%7B%5C%22color%5C%22%3A%5C%22%2354728c%5C%22%7D%7D%22%5D%5D
at Error (native)
at http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:6:450
at k.$get.k.$digest (http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:110:66)
at k.$get.k.$apply (http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:112:173)
at http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:122:253
at e (http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:37:440)
at http://mwm3-gui/asset/script/vendor/angular2.1/angular.min.js:41:120
HTML
<div ng-repeat="panel in row.panels" class="{{panel.columnClass}}" resizable id="{{panel.id}}" r-directions="['right']">
<flot dataset="getDataForChart(panel)" options="getOptionForChart(panel)" height="{{panel.graph.height}}"></flot>
</div>
CONTROLLER
$scope.userTemplate = [
{
blockId: 'blockUno',
title: 'Block title',
rows: [
{
rowId: 'rowUno',
title: 'Row Title 1',
panels: [
{
id: 'palel-report-1',
title: 'uno',
columnClass: 'col-md-4',
graph: {
height: 250,
type: "BAR",
countBy: "status"
}
},
{
id: 'palel-report-2',
title: 'due',
columnClass: 'col-md-4',
graph: {
height: 250,
type: "PIE",
countBy: "status"
}
},
{
id: 'palel-report-3',
title: 'tre',
columnClass: 'col-md-4',
graph: {
height: 250,
type: "BAR",
countBy: "status"
}
}
]
}
],
tables: []
}
];
$scope.getDataForChart = function(panel) {
var graphData = [];
var countBy = panel.graph.countBy;
var arr = $scope.reportingData;
for (var i = 0; i < arr.length; i++) {
var valueOfkey = arr[i][countBy];
graphData.push(valueOfkey);
}
var a = [], b = [], prev;
graphData.sort();
for (var i = 0; i < graphData.length; i++) {
if (graphData[i] !== prev) {
a.push(graphData[i]);
b.push(1);
} else {
b[b.length - 1]++;
}
prev = graphData[i];
}
var graphData = [];
for (var i = 0; i < a.length; i++) {
var singleO = {label: '' + a[i], data: [[i, b[i]]]};
graphData.push(singleO);
}
return graphData;
};
$scope.getOptionForChart = function(panel) {
var options = angular.copy($scope.defaultPlotOptions);
var typeGraph = panel.graph.type;
switch (typeGraph) {
case "BAR":
options.series.bars.show = true;
break;
case "LINE":
options.series.lines.show = true;
break;
case "PIE":
options.series.pie.show = true;
break;
case "POINT":
options.series.points.show = true;
break;
case "TABLE":
break;
}
return options;
};
The error you get is from an infinite digest loop.
In a couple of places you are calling functions that return new items each time. Here's an example from the docs linked from the error message you received that suggests this may cause this error:
One common mistake is binding to a function which generates a new
array every time it is called. For example:
<div ng-repeat="user in getUsers()">{{ user.name }}</div>
$scope.getUsers = function() { return [ { name: 'Hank' }, { name: 'Francisco' } ]; };
Since getUsers() returns a new array, Angular
determines that the model is different on each $digest cycle,
resulting in the error. The solution is to return the same array
object if the elements have not changed:
var users = [ { name: 'Hank' }, { name: 'Francisco' } ];
$scope.getUsers = function() { return users; };
In your code, you are doing the same binding to getDataForChart and getOptionForChart.

How to do the Logic behind the next button in angularjs wizard

I have a customers.create.html partial bound to the WizardController.
Then I have 3 customers.create1,2,3.html partial files bound to WizardController1,2,3
Each WizardController1,2 or 3 has an isValid() function. This function determines wether the user can proceed to the next step.
The next button at the bottom of the pasted html should be disabed if ALL ? isValid() functions are false...
Thats my question but the same time that seems not correct to me.
I guess I am not doing the Wizard correctly...
Can someone please guide me how I should proceed with the architecture that the bottom next button is disabled when the current step isValid function returns false, please.
How can I make a connection from the WizardController to any of the WizardController1,2 or 3 ?
Is Firing an event like broadcast a good direction?
<div class="btn-group">
<button class="btn" ng-class="{'btn-primary':isCurrentStep(0)}" ng-click="setCurrentStep(0)">One</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(1)}" ng-click="setCurrentStep(1)">Two</button>
<button class="btn" ng-class="{'btn-primary':isCurrentStep(2)}" ng-click="setCurrentStep(2)">Three</button>
</div>
<div ng-switch="getCurrentStep()" ng-animate="'slide'" class="slide-frame">
<div ng-switch-when="one">
<div ng-controller="WizardController1" ng-include src="'../views/customers.create1.html'"></div>
</div>
<div ng-switch-when="two">
<div ng-controller="WizardController2" ng-include src="'../views/customers.create2.html'"></div>
</div>
<div ng-switch-when="three">
<div ng-controller="WizardController3" ng-include src="'../views/customers.create3.html'"></div>
</div>
</div>
<a class="btn" ng-click="handlePrevious()" ng-show="!isFirstStep()">Back</a>
<a class="btn btn-primary" ng-disabled="" ng-click="handleNext(dismiss)">{{getNextLabel()}}</a>
'use strict';
angular.module('myApp').controller('WizardController', function($scope) {
$scope.steps = ['one', 'two', 'three'];
$scope.step = 0;
$scope.wizard = { tacos: 2 };
$scope.isFirstStep = function() {
return $scope.step === 0;
};
$scope.isLastStep = function() {
return $scope.step === ($scope.steps.length - 1);
};
$scope.isCurrentStep = function(step) {
return $scope.step === step;
};
$scope.setCurrentStep = function(step) {
$scope.step = step;
};
$scope.getCurrentStep = function() {
return $scope.steps[$scope.step];
};
$scope.getNextLabel = function() {
return ($scope.isLastStep()) ? 'Submit' : 'Next';
};
$scope.handlePrevious = function() {
$scope.step -= ($scope.isFirstStep()) ? 0 : 1;
};
$scope.handleNext = function(dismiss) {
if($scope.isLastStep()) {
dismiss();
} else {
$scope.step += 1;
}
};
});
durandalJS wizard sample code which could be used to rewrite a wizard for angularJS:
define(['durandal/activator', 'viewmodels/step1', 'viewmodels/step2', 'knockout', 'plugins/dialog', 'durandal/app', 'services/dataservice'],
function (activator, Step1, Step2, ko, dialog, app, service) {
var ctor = function (viewMode, schoolyearId) {
debugger;
if (viewMode === 'edit') {
service.editSchoolyear(schoolyearId);
}
else if (viewMode === 'create') {
service.createSchoolyear();
}
var self = this;
var steps = [new Step1(), new Step2()];
var step = ko.observable(0); // Start with first step
self.activeStep = activator.create();
var stepsLength = steps.length;
this.hasPrevious = ko.computed(function () {
return step() > 0;
});
self.caption = ko.observable();
this.activeStep(steps[step()]);
this.hasNext = ko.computed(function () {
if ((step() === stepsLength - 1) && self.activeStep().isValid()) {
// save
self.caption('save');
return true;
} else if ((step() < stepsLength - 1) && self.activeStep().isValid()) {
self.caption('next');
return true;
}
});
this.isLastStep = function() {
return step() === stepsLength - 1;
}
this.next = function() {
if (this.isLastStep()) {
$.when(service.createTimeTable())
.done(function () {
app.trigger('savedTimeTable', { isSuccess: true });
})
.fail(function () {
app.trigger('savedTimeTable', { isSuccess: false });
});
}
else if (step() < stepsLength) {
step(step() + 1);
self.activeStep(steps[step()]);
}
}
this.previous = function() {
if (step() > 0) {
step(step() - 1);
self.activeStep(steps[step()]);
}
}
}
return ctor;
});

How to make ng-repeat filter out duplicate results

I'm running a simple ng-repeat over a JSON file and want to get category names. There are about 100 objects, each belonging to a category - but there are only about 6 categories.
My current code is this:
<select ng-model="orderProp" >
<option ng-repeat="place in places" value="{{place.category}}">{{place.category}}</option>
</select>
The output is 100 different options, mostly duplicates. How do I use Angular to check whether a {{place.category}} already exists, and not create an option if it's already there?
edit: In my javascript, $scope.places = JSON data, just to clarify
You could use the unique filter from AngularUI (source code available here: AngularUI unique filter) and use it directly in the ng-options (or ng-repeat).
<select ng-model="orderProp" ng-options="place.category for place in places | unique:'category'">
<option value="0">Default</option>
// unique options from the categories
</select>
Or you can write your own filter using lodash.
app.filter('unique', function() {
return function (arr, field) {
return _.uniq(arr, function(a) { return a[field]; });
};
});
You can use 'unique'(aliases: uniq) filter in angular.filter module
usage: colection | uniq: 'property'
you can also filter by nested properties: colection | uniq: 'property.nested_property'
What you can do, is something like that..
function MainController ($scope) {
$scope.orders = [
{ id:1, customer: { name: 'foo', id: 10 } },
{ id:2, customer: { name: 'bar', id: 20 } },
{ id:3, customer: { name: 'foo', id: 10 } },
{ id:4, customer: { name: 'bar', id: 20 } },
{ id:5, customer: { name: 'baz', id: 30 } },
];
}
HTML: We filter by customer id, i.e remove duplicate customers
<th>Customer list: </th>
<tr ng-repeat="order in orders | unique: 'customer.id'" >
<td> {{ order.customer.name }} , {{ order.customer.id }} </td>
</tr>
result
Customer list:
foo 10
bar 20
baz 30
this code works for me.
app.filter('unique', function() {
return function (arr, field) {
var o = {}, i, l = arr.length, r = [];
for(i=0; i<l;i+=1) {
o[arr[i][field]] = arr[i];
}
for(i in o) {
r.push(o[i]);
}
return r;
};
})
and then
var colors=$filter('unique')(items,"color");
If you want to list categories, I think you should explicitly state your
intention in the view.
<select ng-model="orderProp" >
<option ng-repeat="category in categories"
value="{{category}}">
{{category}}
</option>
</select>
in the controller:
$scope.categories = $scope.places.reduce(function(sum, place) {
if (sum.indexOf( place.category ) < 0) sum.push( place.category );
return sum;
}, []);
Here's a straightforward and generic example.
The filter:
sampleApp.filter('unique', function() {
// Take in the collection and which field
// should be unique
// We assume an array of objects here
// NOTE: We are skipping any object which
// contains a duplicated value for that
// particular key. Make sure this is what
// you want!
return function (arr, targetField) {
var values = [],
i,
unique,
l = arr.length,
results = [],
obj;
// Iterate over all objects in the array
// and collect all unique values
for( i = 0; i < arr.length; i++ ) {
obj = arr[i];
// check for uniqueness
unique = true;
for( v = 0; v < values.length; v++ ){
if( obj[targetField] == values[v] ){
unique = false;
}
}
// If this is indeed unique, add its
// value to our values and push
// it onto the returned array
if( unique ){
values.push( obj[targetField] );
results.push( obj );
}
}
return results;
};
})
The markup:
<div ng-repeat = "item in items | unique:'name'">
{{ item.name }}
</div>
<script src="your/filters.js"></script>
I decided to extend #thethakuri's answer to allow any depth for the unique member. Here's the code. This is for those who don't want to include the entire AngularUI module just for this functionality. If you're already using AngularUI, ignore this answer:
app.filter('unique', function() {
return function(collection, primaryKey) { //no need for secondary key
var output = [],
keys = [];
var splitKeys = primaryKey.split('.'); //split by period
angular.forEach(collection, function(item) {
var key = {};
angular.copy(item, key);
for(var i=0; i<splitKeys.length; i++){
key = key[splitKeys[i]]; //the beauty of loosely typed js :)
}
if(keys.indexOf(key) === -1) {
keys.push(key);
output.push(item);
}
});
return output;
};
});
Example
<div ng-repeat="item in items | unique : 'subitem.subitem.subitem.value'"></div>
I had an array of strings, not objects and i used this approach:
ng-repeat="name in names | unique"
with this filter:
angular.module('app').filter('unique', unique);
function unique(){
return function(arry){
Array.prototype.getUnique = function(){
var u = {}, a = [];
for(var i = 0, l = this.length; i < l; ++i){
if(u.hasOwnProperty(this[i])) {
continue;
}
a.push(this[i]);
u[this[i]] = 1;
}
return a;
};
if(arry === undefined || arry.length === 0){
return '';
}
else {
return arry.getUnique();
}
};
}
UPDATE
I was recomending the use of Set but sorry this doesn't work for ng-repeat, nor Map since ng-repeat only works with array. So ignore this answer. anyways if you need to filter out duplicates one way is as other has said using angular filters, here is the link for it to the getting started section.
Old answer
Yo can use the ECMAScript 2015 (ES6) standard Set Data structure, instead of an Array Data Structure this way you filter repeated values when adding to the Set. (Remember sets don't allow repeated values). Really easy to use:
var mySet = new Set();
mySet.add(1);
mySet.add(5);
mySet.add("some text");
var o = {a: 1, b: 2};
mySet.add(o);
mySet.has(1); // true
mySet.has(3); // false, 3 has not been added to the set
mySet.has(5); // true
mySet.has(Math.sqrt(25)); // true
mySet.has("Some Text".toLowerCase()); // true
mySet.has(o); // true
mySet.size; // 4
mySet.delete(5); // removes 5 from the set
mySet.has(5); // false, 5 has been removed
mySet.size; // 3, we just removed one value
It seems everybody is throwing their own version of the unique filter into the ring, so I'll do the same. Critique is very welcome.
angular.module('myFilters', [])
.filter('unique', function () {
return function (items, attr) {
var seen = {};
return items.filter(function (item) {
return (angular.isUndefined(attr) || !item.hasOwnProperty(attr))
? true
: seen[item[attr]] = !seen[item[attr]];
});
};
});
Here's a template-only way to do it (it's not maintaining the order, though). Plus, the result will be ordered as well, which is useful in most cases:
<select ng-model="orderProp" >
<option ng-repeat="place in places | orderBy:'category' as sortedPlaces" data-ng-if="sortedPlaces[$index-1].category != place.category" value="{{place.category}}">
{{place.category}}
</option>
</select>
None of the above filters fixed my issue so I had to copy the filter from official github doc. And then use it as explained in the above answers
angular.module('yourAppNameHere').filter('unique', function () {
return function (items, filterOn) {
if (filterOn === false) {
return items;
}
if ((filterOn || angular.isUndefined(filterOn)) && angular.isArray(items)) {
var hashCheck = {}, newItems = [];
var extractValueToCompare = function (item) {
if (angular.isObject(item) && angular.isString(filterOn)) {
return item[filterOn];
} else {
return item;
}
};
angular.forEach(items, function (item) {
var valueToCheck, isDuplicate = false;
for (var i = 0; i < newItems.length; i++) {
if (angular.equals(extractValueToCompare(newItems[i]), extractValueToCompare(item))) {
isDuplicate = true;
break;
}
}
if (!isDuplicate) {
newItems.push(item);
}
});
items = newItems;
}
return items;
};
});
If you want to get unique data based on the nested key:
app.filter('unique', function() {
return function(collection, primaryKey, secondaryKey) { //optional secondary key
var output = [],
keys = [];
angular.forEach(collection, function(item) {
var key;
secondaryKey === undefined ? key = item[primaryKey] : key = item[primaryKey][secondaryKey];
if(keys.indexOf(key) === -1) {
keys.push(key);
output.push(item);
}
});
return output;
};
});
Call it like this :
<div ng-repeat="notify in notifications | unique: 'firstlevel':'secondlevel'">
Add this filter:
app.filter('unique', function () {
return function ( collection, keyname) {
var output = [],
keys = []
found = [];
if (!keyname) {
angular.forEach(collection, function (row) {
var is_found = false;
angular.forEach(found, function (foundRow) {
if (foundRow == row) {
is_found = true;
}
});
if (is_found) { return; }
found.push(row);
output.push(row);
});
}
else {
angular.forEach(collection, function (row) {
var item = row[keyname];
if (item === null || item === undefined) return;
if (keys.indexOf(item) === -1) {
keys.push(item);
output.push(row);
}
});
}
return output;
};
});
Update your markup:
<select ng-model="orderProp" >
<option ng-repeat="place in places | unique" value="{{place.category}}">{{place.category}}</option>
</select>
This might be overkill, but it works for me.
Array.prototype.contains = function (item, prop) {
var arr = this.valueOf();
if (prop == undefined || prop == null) {
for (var i = 0; i < arr.length; i++) {
if (arr[i] == item) {
return true;
}
}
}
else {
for (var i = 0; i < arr.length; i++) {
if (arr[i][prop] == item) return true;
}
}
return false;
}
Array.prototype.distinct = function (prop) {
var arr = this.valueOf();
var ret = [];
for (var i = 0; i < arr.length; i++) {
if (!ret.contains(arr[i][prop], prop)) {
ret.push(arr[i]);
}
}
arr = [];
arr = ret;
return arr;
}
The distinct function depends on the contains function defined above. It can be called as array.distinct(prop); where prop is the property you want to be distinct.
So you could just say $scope.places.distinct("category");
Create your own array.
<select name="cmpPro" ng-model="test3.Product" ng-options="q for q in productArray track by q">
<option value="" >Plans</option>
</select>
productArray =[];
angular.forEach($scope.leadDetail, function(value,key){
var index = $scope.productArray.indexOf(value.Product);
if(index === -1)
{
$scope.productArray.push(value.Product);
}
});

Resources