Slow performance of $watch - angularjs

I am using angular range slider in my project for having the extended Slider functionality.
I am enclosing the relevant code here.
The directive that I am using has this syntax in the minimal version:
<body ng-controller=“MainController as MC”>
<div range-slider
min="0”
max=“MC.maxPrice”
pin-handle="min”
model-max=“MC.price”
>
</div>
</body>
Consider the following code inside the controller:
this.maxPrice = '1000';
this.price = '69’;
$scope.$watch('MC.price', function (newVal) {
if (newVal || newVal === 0) {
for (var i = 0; i < 999; i++) {
console.log('Successful ouput #' + i);
}
}
});
This seems to be working. But it runs slow.
Could somebody suggest me some workaround or suggestion to improve the performance?

I think you can use a temporary model.
You can add a temporary model bound to change the working model on timeout:
<body ng-controller="MainController as MC">
<div range-slider
min="0"
max="MC.maxPrice"
pin-handle="min"
model-max="MC.priceTemporary"
>
</div>
</body>
and modify the controller as:
this.maxPrice = '100';
this.price = '55';
this.priceTemporary = '55';
$scope.$watch('MC.price', function (newVal) {
if (!isNaN(newVal)) {
for (var i = 0; i < 987; i++) {
console.log('Successful ouput #' + i);
}
}
});
var timeoutInstance;
$scope.$watch('MC.priceTemporary', function (newVal) {
if (!isNaN(newVal)) {
if (timeoutInstance) {
$timeout.cancel(timeoutInstance);
}
timeoutInstance = $timeout(function () {
$scope.MC.price = newVal;
}, 144);
}
});

Related

Showing dynamic content inside ngRepeat

Struggling to show dynamic content inside a ngRepeat. When it comes time to show my promise content, I'm getting an empty object {}:
<div ng-controller="DemoCtrl">
<div class="sidebar" ng-repeat="row in rows">
<div class="row">
<input type="checkbox">
<div class="name">{{row.name}}</div>
<div class="title">{{map[$index]}}</div>
</div>
</div>
</div>
and the controller:
function DemoCtrl($scope, $http, $q) {
const rows = function() {
const rows = [];
for (let i = 0; i < 12; i++) {
rows.push({
id: `demo-${i}`,
name: `Demo ${i}`
});
}
return rows;
};
$scope.rows = rows();
$scope.map = [];
// $scope.$watch($scope.map, function (oldValue, newValue) {
// console.log(oldValue, newValue);
// });
function _data() {
// const promises = [];
for (let i = 0; i < $scope.rows.length; i++) {
var defer = $q.defer();
$http.get(`https://jsonplaceholder.typicode.com/posts/${i + 1}`).then(function(post) {
defer.resolve(`${post.data.title.substring(0, 10)}...`);
});
$scope.map.push(defer.promise);
// promises.push(defer.promise);
}
// return $q.all(promises);
return $q.all($scope.map);
}
function _init() {
_data().then(function(data) {
$scope.map = data; // why aren't we getting here?
});
};
_init();
}
Plunker here: https://plnkr.co/edit/2BMfIU97Moisir7BBPNc
I've tinkered with some other ideas such as trying to add a $watch on the $scope object after the value changes, but I'm not convinced this will help in any way. Some lingering questions I have:
From what I understand, you can use a promise inside a template so how/why does this change inside a ngRepeat?
Why isn't my callback for $q.all getting called?
If this is not the right approach, what is?
In Angular you will almost never need to use $q.
You can simply fill an array of posts titles after each $http.get
function DemoCtrl($scope, $http) {
const rows = function () {
const rows = [];
for (let i = 0; i < 12; i++) {
rows.push({
id: `demo-${i}`,
name: `Demo ${i}`
});
}
return rows;
};
$scope.rows = rows();
$scope.map = [];
function _init() {
for (let i = 0; i < $scope.rows.length; i++) {
$http.get(`https://jsonplaceholder.typicode.com/posts/${i + 1}`).then(function (post) {
$scope.map.push(post.data.title);
});
}
}
_init();
}
https://plnkr.co/edit/zOF4KNtAIFqoCOfinaMO?p=preview

How to show star-rating dynamically based on response?

I need to display star rating dynamically based on response.
I am able to display values from 1 to 5 but if rating is 0 then no empty stars are displaying.
If rating = 0.4 also it's showing 1 star filled.
My controller:
(function() {
'use strict';
angular
var app = angular
.module('app')
app.directive('starRating', function () {
return {
restrict: 'A',
template: '<ul class="rating">' +
'<li ng-repeat="star in stars" ng-class="star" ng-click="toggle($index)">' +
'\u2605' +
'</li>' +
'</ul>',
scope: {
ratingValue: '=',
max: '='
},
link: function (scope, elem, attrs) {
var updateStars = function () {
scope.stars = [];
for (var i = 0; i < scope.max; i++) {
if(i == 0) {
scope.stars = [];
scope.stars.push({
empty: i = 0
});
} else {
scope.stars.push({
filled: i < scope.ratingValue
});
}
}
};
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
}
}
});
app.controller('Controller', Controller);
Controller.$inject = ['UserService', '$location', '$rootScope', '$scope', 'fileUpload', 'FlashService', '$cookieStore', '$timeout', '$window'];
function Controller(UserService, $location, $rootScope, $scope, fileUpload, FlashService, $cookieStore, $timeout, $window) {
$scope.selectTestSubject = function() {
$scope.rating = 0;
console.log(levels.length);
for(var k=0; k<levels.length; k++) {
var respRating = levels[k].rating;
// var respRating = 1.5;
console.log(respRating);
$scope.ratings = [{
current: respRating,
max: 5
}];
if(respRating == 0) {
$scope.defaultRating = true;
} else {
$scope.defaultRating = false;
}
}
}
}
}) ();
My HTML page:
<div><span ng-repeat="rating in ratings">
<div star-rating rating-value="rating.current" max="rating.max"></div>
</span>
</div>
One problem with your solution is your $watch expression. Where you have the following:
scope.$watch('ratingValue', function (oldVal, newVal) {
if (newVal) {
updateStars();
}
});
oldVal and newVal are actually the wrong way around, the $watch function first takes in the new value followed by the old value. Secondly, the condition if (newVal) doesn't work for 0, because 0 is a falsey value.
Instead, you should have:
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value has changed.
if (value !== previousValue) {
updateStars();
}
});
Your updateStars function also always reinitialises the scope.stars variable and appends onto it. Doing this can have some unwanted side effects and results in the view not reflecting the model value. It's best to initialise the array, then append the item if it doesn't yet exist or update the existing value. So you'll have something like this:
// Initialise the stars array.
scope.stars = [];
var updateStars = function() {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(scope.ratingValue);
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
Since the $watch expression only updates the stars when the value has changed, you'll now need to trigger the update the first time your link function fires. So this is simply:
// Trigger an update immediately.
updateStars();
Your template also does not correctly utilise the filled property on the star, it should instead contain the appropriate ng-class like so:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-click="toggle($index)">
\u2605
</li>
</ul>
With a simple style,
.star {
cursor: pointer;
color: black;
}
.star.filled {
color: yellow;
}
You can also improve this rating system by listening to mouseenter and mouseleave effects, so that the stars appear yellow when the user is selecting a new value. This is pretty common functionality. You can achieve this by making a few modifications.
To begin with, the template should be updated to listen for these events:
<ul class="rating">
<li class="star"
ng-repeat="star in stars"
ng-class="{ filled: star.filled }"
ng-mouseenter="onMouseEnter($event, $index + 1)"
ng-mouseleave="onMouseLeave($event)"
ng-click="toggle($index)">
\u2605
</li>
</ul>
Next, we want to make a small adjustment to the updateStars function to take in a rating parameter:
var updateStars = function(rating /* instead of blank */ ) {
for (var i = 0; i < scope.max; i++) {
var filled = i < Math.round(rating); // instead of scope.ratingValue
// Check if the item in the stars array exists and
// append it, otherwise update it.
if (scope.stars[i] === undefined) {
scope.stars.push({
filled: filled
});
} else {
scope.stars[i].filled = filled;
}
}
};
// Trigger an update immediately.
updateStars(scope.ratingValue /* instead of blank */ );
scope.$watch('ratingValue', function(value, previousValue) {
// Only update the view when the value changed.
if (value !== previousValue) {
updateStars(scope.ratingValue /* instead of blank */ );
}
});
Now we can add in our event callbacks from the view,
// Triggered when the cursor enters a star rating (li element).
scope.onMouseEnter = function (event, rating) {
updateStars(rating);
};
// Triggered when the cursor leaves a star rating.
scope.onMouseLeave = function (event) {
updateStars(scope.ratingValue);
};
And that's it! Full demo here.

Angular - ngOptions model value not updating

Getting started with Angular and am having an issue getting the model binding to work for a select option in a template.
I have the following ng-options select in a template:
<select ng-model="listenercount" ng-options="n for n in [] | range:0:1000" ng-change="listenersUpdate()"></select>
I have filter which looks like this:
angular.module('myapp').filter('range', function() {
return function(input, min, max) {
min = parseInt(min);
max = parseInt(max);
for (var i=min; i<max; i++) {
input.push(i);
}
return input;
};
});
My select shows up correctly with options 0-1000 based on my filter.
In my controller I have the following:
$scope.listenercount = 0;
$scope.listenersUpdate = function() {
alert('new listener count is now:' + $scope.listenercount);
}
My alert message pops up every time I change the select as expected, but it always show $scope.listenercount = 0. The $scope.listenercount model binding does not seem to be update the value.
Anything obvious I am doing wrong?
<body ng-app="myapp">
<script>
angular.module('myapp', [])
.controller('ExampleController', ['$scope', function($scope) {
$scope.listenercount = 0;
$scope.listenersUpdate = function() {
alert('new listener count is now:' + $scope.listenercount);
}
}]);
angular.module('myapp').filter('range', function() {
return function(input, min, max) {
min = parseInt(min);
max = parseInt(max);
for (var i=min; i<max; i++) {
input.push(i);
}
return input;
};
});
</script>
<div ng-controller="ExampleController">
<select ng-model="listenercount" ng-options="n for n in [] | range:0:1000" ng-change="listenersUpdate()"></select>
<tt>debug = {{confirmed}}</tt><br/>
<tt>counter = {{counter}}</tt><br/>
</div>
</body>
This is working as expected

AngularJs. Cycle in $scope.$watch

I can't understand whats wrong. I need create array of hours and minutes and show him.
HTML:
<div ng-app="test">
<div ng-controller="timeCtrl" ng-init="opentime='9';closetime='24'">
<div ng-repeat="time in times">
{{time}}
</div>
</div>
</div>
JS:
var app = angular.module('test', []);
app.controller('timeCtrl', ['$scope', function ($scope) {
$scope.$watch('opentime', function () {
$scope.times = [];
for (var hours = $scope.opentime; hours < $scope.closetime; hours++) {
console.log(hours);
for (var minutes = 0; minutes < 4; minutes++) {
var linkMinutes = minutes * 15;
if (linkMinutes === 0) {
linkMinutes = "00";
}
console.log(linkMinutes);
$scope.times.push(hours + ':' + linkMinutes);
}
}
});
}])
Why console.log is empty, but vars opentime and closetime with value?
Fiddle: http://jsfiddle.net/Zoomer/mj8zv2qL/
that because your scope variable opentime never been changed to fire watcher
I'v updated the example and simulated the variable change
scope.$watch will execute only when opentime change value please see more here https://docs.angularjs.org/api/ng/type/$rootScope.Scope
and that demo http://jsfiddle.net/oprhy6te/enter link description here
CTRL:
app.controller('timeCtrl', ['$scope', function ($scope) {
$scope.$watch('opentime', function () {
$scope.updateTimes();
});
$scope.updateTimes = function () {
$scope.times = [];
for (var hours = $scope.opentime; hours < $scope.closetime; hours++) {
console.log(hours);
for (var minutes = 0; minutes < 4; minutes++) {
var linkMinutes = minutes * 15;
if (linkMinutes === 0) {
linkMinutes = "00";
}
console.log(linkMinutes);
$scope.times.push(hours + ':' + linkMinutes);
}
}
}
function activate() {
$scope.opentime = 9;
$scope.closetime = 13;
$scope.updateTimes();
}
activate();
}])
HTML:
<div ng-app="test">
<div ng-controller="timeCtrl">
<input type="text" ng-model="opentime" />
<div ng-repeat="time in times"> {{time}}
</div>
</div>
</div>

the diffrence between angular.foreach and "for"

I tried the Todo Example in Angular home page (No.2) and modified a little code which cause a wired problem.
When I add two Todo, the dispaly is ok which is displayed as “4 of 4 remain [archive]”,then I select 2 Todo item and click "acrhive". The result should be “2 of 2 remain [archive]", but acctually the display is “2 of 4 remain [archive]".
Then I replace ”for“ loop wiht "angular.forEach" function, the result is correct.
So anyone can explain what is the diffrence when I use between "for loop" and "angular.forEach"?
The coding is shown as belowing:
<html>
<head>
<meta charset="utf8">
<script src="js/angular.js"></script>
</head>
<body ng-app ng-controller="ToDo">
<span>{{remains()}} of {{total}} remain</span><span>[archive]</span>
<ul>
<li ng-repeat="todo in todos">
<input type="checkbox" ng-model="todo.checked">{{todo.item}}
</input>
</li>
<form ng-submit="addTodo()">
<input type="text" ng-model="toDoitem" placeholder="add Todo List here"></input>
<span><input class="btn-primary" type="submit" value="Add"></input></span>
</form>
</ul>
<script>
var ToDo = function ($scope) {
$scope.todos = [
{item:"Discuss with my team",checked:false},
{item:"Mail to Jack",checked:false}
];
$scope.total = $scope.todos.length;
$scope.remains = function() {
var count =0;
for (var i=0;i<$scope.todos.length;i++) {
count += $scope.todos[i].checked?0:1
};
return count;
};
$scope.action= function($index) {
$scope.todos[$index].checked=!todos[$index].checked
$scope.remain += todos[$index].checked?-1:1;
};
$scope.addTodo = function() {
$scope.total ++;
$scope.todos.push({item:$scope.toDoitem,checked:false});
$scope.toDoitem = '';
};
$scope.archive = function() {
var oldTodos = $scope.todos;
$scope.todos = [];
for (var i=0;oldTodos.length-1;i++){
if (!oldTodos[i].checked) {
$scope.todos.push({item:oldTodos[i].item,checked:false});
};
};
/*
angular.forEach(oldTodos,function(todo){
if (!todo.checked) {
$scope.todos.push(todo);
};
});
*/
$scope.total = $scope.todos.length;
};
};
//http://jsfiddle.net
//http://plnkr.co/
</script>
</body>
</html>
In the context of your example there is nothing in particular different between angular.forEach and a standard for loop.
You don't appear to be pushing the same object into $scope.todos in the forEach, does the following help:
angular.forEach(oldTodos, function(todo) {
if (!todo.checked) {
$scope.todos.push({item: todo.item, checked: false});
};
});
You just need to have a look at the implementation for forEach for arrays and compare it with yours:
Angularjs forEach implementation for arrays:
for (key = 0; key < obj.length; key++)
iterator.call(context, obj[key], key);
And yours:
for (var i=0;oldTodos.length-1;i++){
if (!oldTodos[i].checked) {
$scope.todos.push({item:oldTodos[i].item,checked:false});
};
};
You could try:
for (var i=0; i < oldTodos.length; i++){
if (!oldTodos[i].checked) {
$scope.todos.push({item:oldTodos[i].item,checked:false});
};
};

Resources