Im pretty new to angularJS so please forgive me if I say anything completely wrong. I making some sort of a chat. I have a problem with the scope/view not beign updated after getting the data. I have a variable $scope.users which is an array containing infos about users and the view does an ng-repeat on the users to display their infos.
The data is stored from a previous page and loaded through session storage.
The controller looks like:
app.controller('table-controller',['$scope','$interval','HttpService','TableModel',
function($scope,$interval,HttpService,TableModel){
var _this = this;
console.log('loading table controller ...');
$scope.users = [];
var sessionData = JSON.parse(sessionStorage.getItem('sessionData'));
var activeUsers = sessionData.activeUsers;
//$scope.users = TableModel.initUsers();
$scope.$watch('users',function(){
for(var i = 0; i < 10; i++){
$scope.users.push({
user : ' ',
name : 'Vacant',
picture : '../img/sit_default.png',
speak_turn : ' ',
sit : i,
last_request : 0
});
}
for(var i = 0; i < activeUsers.length; i++){
var sit = activeUsers[i].sit;
$scope.users[sit] = activeUsers[i];
}
});
$scope.className = function(index){
return "sit" + (index + 1);
}
And the html is :
<ul class="first-place">
<li ng-repeat="user in users track by $index" ng-class="className($index)">
<div class="pro_pic_wrap">
<img ng-src="{{user.picture}}" alt="">
<span>{{$index + 1}}</span>
</div>
{{user.name}}
</li>
</ul>
Everything works fine if I use $watch to create $scope.users. Otherwise when the page loads the view sees an empty $scope.users so ng-repeat does nothing. If i reload the page everything works fine. I tried $scope.$apply() but gives me an error saying digest is already being called. I dont understand why I need to use $watch here.
Thanks in advance for your help!
you can create a method and call it instead of using $watch
$scope.getUsers= function(){
for(var i = 0; i < 10; i++){
$scope.users.push({
user : ' ',
name : 'Vacant',
picture : '../img/sit_default.png',
speak_turn : ' ',
sit : i,
last_request : 0
});
}
for(var x = 0; x < users.length; x++){
var sit = activeUsers[x].sit;
$scope.users[sit] = activeUsers[x];
}
}
$scope.getUsers();
Related
I have a search field where I want to display products based on the keyword(s). So far products are being shown which match EITHER one of the keywords but I want the search results displayed ONLY when ALL keywords are matched. I tried to do a for loop through the arguments (the input /keywords)and used angulars filter but its not really working. Any ideas?
var app=angular.module('app', []);
app.controller('appController', function($scope, $http) {
$scope.search = function(key) {
function listOfItems(key) {
var array = Array.prototype.slice.call(arguments);
var keywords = "";
for (var i = 0; i < array.length; i++) {
keywords += array[i] + '+'
}
return keywords.slice(0, keywords.length - 1);
}
$scope.parameter = listOfItems(key);
$http.('http://example.com/store/?keyword=' + $scope.parameter + '&token=11111111.22222')
.then(function(prods) {
return prods.data;
if($scope.parameter){
$scope.prods = prods.data;
}
})
}
})
html:
<input ng-model="key" type="text" class="form-control>
<button type=" submit " ng-click="search(key) ">
</button>
<div ng-repeat="shop in shops | filter: search(key) ">
<p>{{shop.title}}</p>
<p>{{shop.description}}</p>
</div>
I have an ng-repeat as following :
<ul>
<li ng-repeat="o in villes | limitTo:5">{{o.nomVille}}</li>
</ul>
I want to reorder the villes list randomly before I limit it to 5, so every time I open my page I get 5 different villes each time.
is there a filter in angularjs who can do that for me ?
edit :
I created a costum filter to randomize that list as following :
.filter('random', function() {
return function(val) {
let shuffle = (a) => {
let r = [];
while (arr.length)
r.push(
arr.splice( (Math.floor(Math.random() * arr.length)) , 1)[0]
);
return shuffle(val);
}
};
});
and in ng-repeat I did this :
<li ng-repeat="o in villes | random | limitTo:5">{{o.nomVille}}</li>
but I cant no longer see anything in my page.
this is the example on jsfiddle : https://jsfiddle.net/z10wwhcv/
You'd have to build a custom filter function that randomizes the order and then apply the filters in the correct order (random before limit).
Have a look at this for details:
Using AngularJS how could I randomize the order of a collection?
If you want the order to change each time you load the page you can't do it as a filter as that will presumably change on each digest cycle. You need to store villes on the scope somewhere and generate it in a random order when the page loads, e.g. in your controller function for that page.
use the filter in the controller (which is also a best practice performance boost):
$scope.shuffled = $filter('random',$scope.villes)
you'll need to inject the service in the controller
this is what your filter should look like ( not tested but should work ):
.filter('random', function() {
return function(a) {
var r = [];
while (a.length)
r.push(
a.splice((Math.floor(Math.random() * a.length)), 1)[0]
);
return r;
}
}
This is the solution I created :
shufflemodule.filter('shuffle', function() {
var shuffledArr = [],
shuffledLength = 0;
return function(arr) {
var o = arr.slice(0, arr.length);
if (shuffledLength == arr.length) return shuffledArr;
for(var j, x, i = o.length; i; j = parseInt(Math.random() * i), x = o[--i], o[i] = o[j], o[j] = x);
shuffledArr = o;
shuffledLength = o.length;
return o;
};
});
http://jsfiddle.net/aimad_majdou/6mngvo38/
Hopefully someone can point my in the right direction.
I am building a web app and part of it requires a user to click a button as fast as they can to obtain a score. The design dictates that I will need to show this score in double digits i.e 9 would be 09 so for styling I need to wrap span tags around each digit.
I have got everything working as required, I'm just having an issue with outputting the score that is wrapped in span tags as rendered html in my view.
I've put together a fiddle for the section that is causing me problems. Any advice, help, best practices etc is much appreciated.
What I've tried:
I've included a few of the things I've tried. Basically they involve using $sce and trying to ng-bind-html in the view. Attempt 3 seems the most logical to me but the $scope.count isn't being updated. I'm guessing I need to add a $watch or $apply function to keep it binded? but I'm not too sure how to implement it or even if this is good practice. Also, because I'm outputting html is it better practice to do this in a directive?
Fiddle http://jsfiddle.net/funkycamel/gvxpnvqp/4/
HTML
<section ng-app="myApp">
<div ng-controller="MyController">
<button ng-click="add(1)">add</button>
<!-- First attempt -->
<p class="first-attempt">{{ pad(count) }}</p>
<!-- Second attempt -->
<!-- in order for this attempt to work I have to call the pad2 function which
returns trustedHtml -->
{{ pad2(count) }}
<p class="second-attempt" ng-bind-html="trustedHtml"></p>
<!-- Third attempt -->
<p class="third-attempt" ng-bind-html="moreTrustedHtml"></p>
</div>
Javascript
var app = angular.module('myApp', []);
app.controller('MyController', ['$scope', '$sce', function ($scope, $sce) {
// Set initial count to 0
$scope.count = 0;
// function to add to $scope.count
$scope.add = function (amount) {
$scope.count += amount;
};
// Attempt 1
// make sure number displays as a double digit if
// under 10. convert to string to add span tags
$scope.pad = function (number) {
var input = (number < 10 ? '0' : '') + number;
var n = input.toString();
var j = n.split('');
var newText = '';
var trustedHtml = '';
for (var i = 0; i < n.length; i++) {
newText += '<span>' + n[i] + '</span>';
}
return newText;
};
// Attempt 2 - trying to sanitise output
// same as above just returning trusted html
$scope.pad2 = function (number) {
var input = (number < 10 ? '0' : '') + number;
var n = input.toString();
var j = n.split('');
var newText = '';
var trustedHtml = '';
for (var i = 0; i < n.length; i++) {
newText += '<span>' + n[i] + '</span>';
}
// return sanitised text, hopefully
$scope.trustedHtml = $sce.trustAsHtml(newText);
return $scope.trustedHtml;
};
// Attempt 3
// Trying to sanitise the count variable
$scope.moreTrustedHtml = $sce.trustAsHtml($scope.pad($scope.count));
}]);
These currently output
<span>0</span><span>0</span>
<span>0</span><span>0</span>
00
00
Again any advice/help is greatly appreciated.
Far simpler solution:
HTML
<p>{{ count < 10 ? '0' + count : count}}</p>
Controller:
app.controller('MyController', ['$scope', function ($scope) {
$scope.count = 0;
$scope.add = function (amount) {
$scope.count += amount;
};
}]);
DEMO
If you prefer you can do the padding in the controller instead, just use another variable
app.controller('MyController', ['$scope', function ($scope) {
var count = 0;
$scope.countText = '0';
$scope.add = function (amount) {
count += amount;
$scope.countText = count < 10 ? '0' + count : count;
};
}]);
Is there a way to loop through multiple arrays in an ng-repeat directive ?
I tried something like
<ul>
<li ng-repeat="sale in randomSales" ng-repeat="image in imageUrls">
<div display-sale></div>
</li>
</ul>
or
<ul>
<li ng-repeat="sale in randomSales, image in imageUrls">
<div display-sale></div>
</li>
</ul>
but it's not working.
I could solve this issue another way, but I'd like to know if this is possible !
EDIT :
Here is my controller & directive :
app.controller('RandomController', ['$rootScope', '$scope',function($rootScope, $scope) {
$scope.randomSales=[];
$scope.imageUrls = [];
for(var i= 0; i < displayrandomSalesNumber ; i++){
var randomNumber = Math.floor((Math.random() * $rootScope.sales.length-1) + 1);
$scope.randomSales.push($rootScope.sales[randomNumber]);
$scope.imageUrls.push($rootScope.sales[randomNumber].image_urls["300x280"][0].url);
}
console.log($scope.randomSales);
}])
.directive('displaySale', function() {
return {
template: '<div class="center"><a href="#/sale/{{sale.store}}/{{sale.sale_key}}">' +
'<header><h2>{{sale.name}}</h2><h4>in {{sale.store}}</h4></header>' +
'<article>' +
'<p>From : {{sale.begins}} To : {{sale.ends}}</p>' +
'<p class="center"><img ng-src="{{image}}"/></p>' +
'<p>{{sale.description}}</p>' +
'</article>' +
'</a></div>'
};
});
the image is already inside $scope.randomSales, I could access it with {{sale.image_urls["300x280"][0].url}} but i'd get a parse error.
to make it work you can try to do following:
- merge 2 arrays to one arrays of objects representing imageUrls and randomSales.
- iterate though them in your template.
For example:
var maxArrayLength = Math.max(randomSales.length, imageUrls.length);
var i = 0;
var result = []; //will put items {sale: ..., image}
for (i =0; i < maxArrayLength; i++){
var currentSale = randomSales[i] != null ? randomSales[i] : null;
var currentImage = imageUrls[i] != null ? imageUrls[i] : null;
result.push({sale: currentSale, image: currentImage})
}
return result;
And them you can use this array to iterate through on your angular template.
The First Solution proposed by you is wrong , we cant have two ng-repeat attributes in one Html tag, I am not sure about the second one . But you could try like this ..i think it would work.
<ul>
<li ng-repeat="sale in randomSales">
<span ng-repeat="image in imageUrls">
<div display-sale></div>
</span>
</li>
</ul>
I used ng-resource to get data from my server and then place the data into a table grid like this:
<div ng-form name="grid">
<button type="submit" data-ng-disabled="grid.$pristine">Save</button>
<div class="no-margin">
<table width="100%" cellspacing="0" class="form table">
<thead class="table-header">
<tr>
<th>ID</th>
<th>Title</th>
</tr>
</thead>
<tbody class="grid">
<tr data-ng-repeat="row in grid.data">
<td>{{ row.contentId }}</td>
<td><input type="text" ng-model="row.title" /></td>
</tr>
</tbody>
</table>
</div>
</div>
Is there a way that I can make it so that clicking on the Submit button checks through the grid for the rows that changed and then calls a putEntity(row) function with the row as an argument?
You could do it a few ways, and remember every NgModelController has a $dirty flag which can use to check if the input has changed. But I would say the easiest way is just to do this:
Edit to HTML:
<input type="text" ng-model="row.title" ng-change="row.changed=true" />
<button ng-click="save()">Save</button>
In JS:
$scope.save = function () {
// iterate through the collection and call putEntity for changed rows
var data = $scope.grid.data;
for (var i = 0, len = data.length; i < len; i++) {
if (data[i].changed) {
putEntity(data[i]);
}
}
}
Here's something that could work. It is built with the JSFiddle from the first comment as a basis.
First, I changed the data-ng-disabled attribute to changes.length <= 0 and added $scope.changes = [] to the controller.
$scope.changes = [];
Then I added a watch on $scope.data
$scope.$watch('data', function(newVal, oldVal){
for(var i = 0; i < oldVal.length; i++){
if(!angular.equals(oldVal[i], newVal[i])){
console.log('changed: ' + oldVal[i].name + ' to ' + newVal[i].name);
var indexOfOld = $scope.indexOfExisting($scope.changes, 'contentId', newVal[i].contentId);
if(indexOfOld >= 0){
$scope.changes.splice(indexOfOld, 1);
}
$scope.changes.push(newVal[i]);
}
}
}, true); // true makes sure it's a deep watch on $scope.data
Basically this runs through the array and checks if anything has changed using angular.equals. If an object has changed it is checked if it exists in $scope.changes already. If it does, it's removed. After that newVal[i] is pushed to $scope.changes
The $scope.indexOfExisting is taken from this SO question
$scope.indexOfExisting = function (array, attr, value) {
for(var i = 0; i < array.length; i += 1) {
if(array[i][attr] === value) {
return i;
}
}
};
Finally I made the $scope.checkChange() look like so
$scope.checkChange = function(){
for(var i = 0; i < $scope.changes.length; i++){
console.log($scope.changes[i].name);
//putEntity($scope.changes[i])
}
$scope.changes = [];
};
This will then give you the ability to submit only the changed rows.
I decided to do this as follows:
Here is where I get the data and then make a copy of it:
getContents: function ($scope, entityType, action, subjectId, contentTypeId, contentStatusId) {
entityService.getContents(entityType, action, subjectId, contentTypeId, contentStatusId)
.then(function (result) {
$scope.grid.data = result;
$scope.grid.backup = angular.copy(result);
$scope.grid.newButtonEnabled = true;
}, function (result) {
alert("Error: No data returned");
$scope.grid.newButtonEnabled = false;
});
},
Then later when it comes to saving I use angular.equals to compare with a backup:
$scope.saveData = function () {
var data = $scope.grid.data;
for (var i = 0, len = $scope.grid.data.length; i < len; i++) {
if (!angular.equals($scope.grid.data[i], $scope.grid.backup[i])) {
var rowData = $scope.grid.data[i]
var idColumn = $scope.entityType.toLowerCase() + 'Id';
var entityId = rowData[idColumn];
entityService.putEntity($scope.entityType, entityId, $scope.grid.data[i])
.then(function (result) {
angular.copy(result, $scope.grid.data[i]);
angular.copy(result, $scope.grid.backup[i]);
}, function (result) {
alert("Error: " + result);
})
}
}
$scope.grid.newButtonEnabled = true;
$scope.grid.saveButtonEnabled = false;
$scope.$broadcast('tableDataSetPristine');
}
I did something quite similar for myself, and I used a different way, I decided to directly bind all the ng-models to the data, which let me work with the indexes if I need to check every row, but I could also decide to send the whole data to the server
Then, I just have to watch all the changes made to every index (even with big data, I just have to add/remove an integer) and store them in an array
Once finished, I can click the submit button, which will loop on every index I've touched, to check if the content is really different from the original one, and then I can do whatever I want with it (like calling a putEntity function, if I had one)
I made a fiddle based on your html code, it will be easier to play with it than with a copy/paste of the code here : http://jsfiddle.net/DotDotDot/nNwqr/1/
The main change in the HTML is this part, binding the model to the data and adding a change listener
<td><input type="text" ng-model="data[$index].title" ng-change="notifyChange($index)"/></td>
I think the javascript part is quite understandable, I log indexes while the data is modified, then, later, when I click on the submit button, it can call the right function on the modified rows only, based on the logged indexes