I'm new in AngularJS and i'd like to know why, in the next piece of code, the "randomN" property of the service is not updating at the $scope.
Since i'm taking a reference to the object's service at the controller with
$scope.srvRandom = srvRandom;
Why changes at srvRandom.randomN are not taking effect?
I think the problem is that "randomN" takes the value of "r" and not a reference to it, but i don't know how to make it work.
var app = angular.module("app", []);
app.controller("cont", ['$scope','srvRandom', function ($scope, srvRandom) {
$scope.srvRandom = srvRandom;
}]);
app.factory("srvRandom", ["$interval", function ($interval) {
var randomCreator;
var r;
return {
start: function () {
console.log("START")
randomCreator = $interval(function () {
r= Math.random() * 10;
}, 2000);
},
stop: function () {
console.log("STOP")
$interval.cancel(randomCreator);
},
randomN: r
};
}]);
HTML:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title></title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.5.0-rc.2/angular.min.js"></script>
<script src="test.js"></script>
</head>
<body ng-app="app" ng-controller="cont">
RandomNumber: {{srvRandom.randomN}}
<div>
<button ng-click="srvRandom.start()">START!</button>
<button ng-click="srvRandom.stop()">STOP!</button>
</div>
</body>
</html>
Thank you!
You are accessing srvRandom.randomN which is always 0.
...
randomN: 0
...
I think you should do
app.factory("srvRandom", ["$interval", function ($interval) {
var randomCreator,
randomN = 0; // Change
return {
start: function () {
console.log("START")
randomCreator = $interval(function () {
randomN = Math.random() * 10;
}, 2000);
},
stop: function () {
console.log("STOP")
$interval.cancel(randomCreator);
},
randomN: randomN // Change
};
}]);
just update your start method to use this for current object not window object.
start: function() {
var that = this; //store this reference to a variable
randomCreator = $interval(function() {
that.randomN = Math.random() * 10; // use here
}, 2000);
},
doing this $scope.Random = srvRandom; create a local instance of the service obj
var app = angular.module("app", []);
app.controller("cont", ['$scope', 'srvRandom',
function($scope, srvRandom) {
$scope.random = 0;
$scope.$watch('getRandom()',function(newv){
$scope.random = newv;
});
$scope.start = function(){
srvRandom.start();
}
$scope.stop = function(){
srvRandom.stop();
}
$scope.getRandom = function(){
return srvRandom.getRandom();
}
}
]);
app.factory("srvRandom", ["$interval",
function($interval) {
var randomCreator;
var randomN = 0;
return {
start: function() {
console.log("START")
randomCreator = $interval(function() {
randomN = Math.random() * 10;
}, 2000);
},
stop: function() {
console.log("STOP")
$interval.cancel(randomCreator);
},
getRandom: function() {
return randomN;
}
};
}
]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body ng-app="app" ng-controller="cont">
RandomNumber: {{random}}
<div>
<button ng-click="start()">START!</button>
<button ng-click="stop()">STOP!</button>
</div>
</body>
Related
My directive with controller :
app.directive("photoGallery", function() {
return {
restrict: 'E',
templateUrl: 'part/photoGallery.html',
controller: ["$http", function($http) {
me = this;
this.autoSlide = true;
this.currentIndex = 2;
this.autoSlide_timer;
this.photos = [{}];
this.Show = function(index) {
me.currentIndex = index;
};
this.Next = function() {
me.currentIndex++;
console.log(me.currentIndex);
if (me.currentIndex >= me.photos.length) {
me.currentIndex = 0;
}
me.Show(me.currentIndex);
};
this.Prev = function() {
me.currentIndex--;
if (me.currentIndex < 0) {
me.currentIndex = me.photos.length-1;
}
me.Show(me.currentIndex);
};
this.Init = function() {
$http.get("img/slider/_data.json")
.success(function(data) {
me.photos = data;
for(var i in me.photos) {
me.photos[i].index = i;
}
console.info(me.photos);
})
.error(function(e){
console.error(e);
});
this.autoSlide_timer = setInterval(this.Next, 1500);
}();
}],
controllerAs: 'gallery'
};
});
photoGallery.html :
<div class="GalleryContainer">{{gallery.currentIndex}}
<div class="PhotoWrapper">
<div ng-repeat="photo in gallery.photos" class="photo" ng-class="{active:gallery.currentIndex == photo.index}">
<img ng-src="img/slider/{{photo.path}}">
</div>
</div>
</div>
as you can see, in the Next() function, I log currentIndex
Next() is called every 1500ms thanks to setInterval(this.Next, 1500) in the Init() function.
I can see in the console : 2, 3, 4, ... 10, 0, 1, 2 ...
But in the browser, {{gallery.currentIndex}} is never updated, it display the default value (2) (photoGallery.html line 1)
You have to use angular $interval() instead of JavaScript setInterval() function. Why ?
Because Angular needs to know when variables are updated. $interval make a call to $scope.$apply() at the end of its execution, executing a new $digest loop that notify the update to angular.
You can also wait the request to be successfully proceed to set you interval, to avoid errors.
this.Init = function() {
$http.get("img/slider/_data.json")
.success(function(data) {
me.photos = data;
for(var i in me.photos) {
me.photos[i].index = i;
}
console.info(me.photos);
me.autoSlide_timer = $interval(me.Next, 1500);
})
.error(function(e){
console.error(e);
});
}();
I'm looking on how to make a 60 seconds countdown using angular js.
I want to show the countdown on the page ! and when the countdown is finished, the controller should reload to execute the code again ! and get the update json object !
my controller looks like :
.controller('todaymatches', function($rootScope,$scope, $http) {
$http.get("http://www.domaine.com/updatedjson/")
.success(function (response) {
$scope.matches = response;
});
})
I'm made a code ! I'm not sure if this works properly ! anyway it's not working on my app.
$scope.countdown = function() {
stopped = $timeout(function() {
console.log($scope.counter);
$scope.counter--;
$scope.countdown();
}, 1000);
};
Here is a simple countdown example:
HTML
<!doctype html>
<html ng-app>
<head>
<script src="http://code.angularjs.org/angular-1.0.0rc11.min.js"></script>
<script src="http://documentcloud.github.com/underscore/underscore-min.js"></script>
</head>
<body>
<div ng-controller="CountdownController">
{{counter}}
</div>
</body>
</html>
Javascript
function CountdownController($scope,$timeout) {
$scope.counter = 60;
$scope.onTimeout = function(){
if ($scope.counter > 0) {
$scope.counter--;
mytimeout = $timeout($scope.onTimeout,1000);
} else {
$scope.counter = 60;
}
}
var mytimeout = $timeout($scope.onTimeout,1000);
}
Demo
'use strict';
var ngApp = angular.module('myApp', ['Test']);
var c1 = angular.module('Test', []);
c1.controller('Ctrl1', function ($scope, $timeout) {
$scope.coutDown = function () {
$scope.onTimeout = function () {
console.log("value", $scope.value);
$scope.value = $scope.value - 1;
return $scope.coutDown($scope.value);
};
var delay = $timeout($scope.onTimeout, 1000);
if ($scope.value < 1) {
$timeout.cancel(delay);
return true;
}
return false;
};
$scope.value = 5;
$scope.coutDown();
});
<div ng-app="myApp">
<div ng-controller="Ctrl1">
<h1>{{value}}</h1>
</div>
</div>
http://jsfiddle.net/pbxaD/49/
if you want to use $timeout you have to inject it. But why don't you just call the update method in a certain interval?
.controller('todaymatches', function($rootScope,$scope, $http, $interval) {
var update = function() {
$http.get("http://www.domaine.com/updatedjson/")
.success(function (response) {
$scope.matches = response;
});
};
var initialize = function() {
$interval(function() {
update();
}, 60 * 1000)
};
initialize();
})
I tried this for the count down and it seems to work.
app.controller('CountDownController', function($scope, $timeout) {
$scope.counter = 60;
$scope.countdown = function() {
if ($scope.counter === 0) {
// do your reload and execute here
//Just reset the counter if you just want it to count again
$scope.counter = 60;
return;
} else {
$timeout(function() {
console.log($scope.counter);
$scope.counter--;
$scope.countdown();
}, 1000);
}
};
$scope.countdown();
});
You could tie up the various things you want to do inside the if condition of the above code as commented. I just reset the counter after counting down to 0.
I coded the below directive for infinite scroll, my problem which I couldn't figure out why it just fire once when the directive is loaded, I need your advice on how to make my list infinite-scroll.
I'm using it to get data remotely and each time i'm calling it I add to the counter 25, so each time it would return more data.
Thanx,
angular.module('MyApp')
.controller('InboxCtrl', function($scope, InboxFactory) {
var counter = 0;
$scope.loadData = function() {
var promise = InboxFactory.getEvents(counter);
promise.then(function(result) {
$scope.events = result;
});
counter += 25;
};
});
angular.module('MyApp')
.factory('InboxFactory', function($http, $q) {
// Service logic
var defered = $q.defer();
function getUrl(count) {
return "api/inbox/get?request={'what':'Search','criteria':'inbox','criteriaId':null,'startTime':null,'endTime':null,'offset':" + count + ",'limit':25,'order':'event_time','direction':'DESC','source':''}";
}
function extract(result) {
return result.data.data;
}
// Public API here
return {
getEvents: function(count) {
$http.get(getUrl(count)).then(
function(result) {
defered.resolve(extract(result))
}, function(err) {
defered.reject(err);
}
);
return defered.promise;
}
};
});
angular.module('MyApp')
.directive('infiniteScroll', ['$timeout',
function(timeout) {
return {
link: function(scope, element, attr) {
var
lengthThreshold = attr.scrollThreshold || 50,
timeThreshold = attr.timeThreshold || 400,
handler = scope.$eval(attr.infiniteScroll),
promise = null,
lastRemaining = 9999;
lengthThreshold = parseInt(lengthThreshold, 10);
timeThreshold = parseInt(timeThreshold, 10);
if (!handler || !components.isFunction(handler)) {
handler = components.noop;
}
element.bind('scroll', function() {
var
remaining = element[0].scrollHeight - (element[0].clientHeight + element[0].scrollTop);
//if we have reached the threshold and we scroll down
if (remaining < lengthThreshold && (remaining - lastRemaining) < 0) {
//if there is already a timer running which has no expired yet we have to cancel it and restart the timer
if (promise !== null) {
timeout.cancel(promise);
}
promise = timeout(function() {
handler();
promise = null;
}, timeThreshold);
}
lastRemaining = remaining;
});
}
};
}
]);
<ul class="inbox-list" infinite-scroll="loadData()">
<li class="clearfix" ng-repeat="event in events">{{event}}</li>
</ul>
I Made some changes the more important is the use of ng-transclude and the creation of a new scope for the directive to pass the method and the parameters. You can have a look at the jsbind. Of course the data are hard coded so i could fake the behaviour.
<ul class="inbox-list" my-infinite-scroll composite-method="loadData()">
I have a list of elements. When I click an up or down icon, I would like the list to rearrange itself and, finally, for the app to rerender itself so I can see the change reflected in the DOM.
Changing list position works. I'm running into issues when I try to run the refreshState method. I'm passing the function as a property of the child but calling that property returns undefined function.
Q: How do I call a component's method from its child component?
Code:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<style>
/* CSS */
span {
margin:0 0 0 10px;
}
</style>
<div id="app"></div>
<script type="text/jsx">
// React
var _data = ['Red', 'Blue', 'Green'];
function getState() {
return {
colors: _data,
}
};
Array.prototype.swap = function(a, b) {
var temp = this[a];
this[a] = this[b];
this[b] = temp;
};
var App = React.createClass({
getInitialState: function() {
return getState();
},
render: function() {
var colors = this.state.colors.map(function(color) {
return (
<Color name={color} refreshState={this.refreshState} />
)
});
return (
<ul>{colors}</ul>
)
},
refreshState: function() {
return this.setState(getState());
},
});
var Color = React.createClass({
moveUp: function() {
var current = _data.indexOf(this.props.name),
above = current - 1;
if (above >= 0) {
_data.swap(current, above);
}
return this.props.refreshState();
},
moveDown: function() {
var current = _data.indexOf(this.props.name),
below = current + 1;
if (below < _data.length) {
_data.swap(current, below);
}
return this.props.refreshState();
},
render: function() {
return (
<li>
<strong>{this.props.name}</strong>
<span onClick={this.moveUp}>^</span>
<span onClick={this.moveDown}>v</span>
</li>
)
},
});
React.render(<App />, document.getElementById('app'));
</script>
</body>
</html>
Noticed you have solved the question, but thought I'd mention that you can pass a scope to .map which means no need to cache scope for the purpose you describe:
this.state.colors.map(function(color) {
return (
<Color
key={_data.indexOf(color)}
name={color}
refresh={this.refreshState}
/>
)
}, this); // pass the scope to .map
I was trying to call this.refreshState within the map method. This, of course, does not have the same scope as the render method. The solution was to store the scope in a variable:
var refresh = this.refreshState;
Then use that variable within the map method:
... refreshState={refresh} ...
Always be aware of your scope!
If you have multiple functions that aren't within the local scope then you can store this in a variable.
var self = this;
z.map(function(arg) {
x={self.refreshState} y={self.otherThing}
And for the curious, the finished result:
<!doctype html>
<html>
<head>
<meta charset="UTF-8">
<title>Test</title>
<script src="http://fb.me/react-with-addons-0.12.2.js"></script>
<script src="http://fb.me/JSXTransformer-0.12.2.js"></script>
</head>
<body>
<style>
/* CSS */
span {
margin:0 0 0 10px;
}
</style>
<div id="app"></div>
<script type="text/jsx">
// React
var _data = ['Red', 'Blue', 'Green'];
function getState() {
return {
colors: _data,
}
};
Array.prototype.swap = function(a, b) {
var temp = this[a];
this[a] = this[b];
this[b] = temp;
};
var App = React.createClass({
getInitialState: function() {
return getState();
},
refreshState: function() {
return this.setState(getState());
},
render: function() {
var self = this;
var colors = this.state.colors.map(function(color) {
return (
<Color
key={_data.indexOf(color)}
name={color}
refresh={self.refreshState}
/>
)
});
return (
<ul>{colors}</ul>
)
},
});
var Color = React.createClass({
propTypes: {
name: React.PropTypes.string,
refresh: React.PropTypes.func.isRequired,
},
moveUp: function() {
var current = _data.indexOf(this.props.name),
above = current - 1;
if (above >= 0) {
_data.swap(current, above);
}
return this.props.refresh();
},
moveDown: function() {
var current = _data.indexOf(this.props.name),
below = current + 1;
if (below < _data.length) {
_data.swap(current, below);
}
return this.props.refresh();
},
render: function() {
return (
<li>
<strong>{this.props.name}</strong>
<span onClick={this.moveUp}>^</span>
<span onClick={this.moveDown}>v</span>
</li>
)
},
});
React.render(<App />, document.getElementById('app'));
</script>
</body>
</html>
I have a timeout loop like this:
var somedata = {
autoRefreshInterval: 300,
autoRefreshInSec: 300,
myTimeout: null,
doRefresh: _doRefresh,
onTimeout: function () {
this.autoRefreshInSec--;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
this.doRefresh();
}
this.myTimeout = $timeout(this.onTimeout, 1000);
},
startTimer: function () {
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout, 1000);
},
stopTimer: function () {
$timeout.cancel(this.myTimeout);
},
}
It appears that the "this" doesn't work inside of onTimeout callback function, while it works fine for startTimer and stopTimer. How to fix it?
UPDATE:
Since this is lost inside the onTimeout based on one of the answers below, I tried to pass it into like this:
onTimeout: function (self) {
self.autoRefreshInSec--;
if (self.autoRefreshInSec <= 0) {
self.autoRefreshInSec = self.autoRefreshInterval; // Set it here so that it does't start refresh right away. It will be reset when refresh is done.
self.doRefresh();
}
self.myTimeout = $timeout(self.onTimeout(self), 1000);
},
startTimer: function () {
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout(this), 1000);
},
Strangely, when I debug through the code, it seems working. However, once I removed the break points, self.doRefresh() is fired continuously. Why?
UPDATE 2:
Okay, I created a JSFiddle at http://jsfiddle.net/qY86q/1 to illustrate the problem.
Function.prototype.bind()
The bind() method creates a new function that, when called, has its this keyword set to the provided value, with a given sequence of arguments preceding any provided when the new function is called.
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Function/bind
Solution for your case
angular.module('myApp', [])
.service('timerService', function($timeout) {
var _timer = {
autoRefreshInterval: 300,
autoRefreshInSec: 300,
myTimeout: null,
onTimeout: function() {
this.autoRefreshInSec -= 1;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
console.log('refreshing');
}
console.log('time: ', this.autoRefreshInSec);
this.myTimeout = $timeout(this.onTimeout.bind(this), 1000);
},
startTimer: function() {
if (this.myTimeout) {
this.stopTimer(this.myTimeout)
}
this.autoRefreshInSec = this.autoRefreshInterval;
this.myTimeout = $timeout(this.onTimeout.bind(this), 1000);
},
stopTimer: $timeout.cancel // see note(1)
};
var context = {
timer: _timer
};
return context;
}).controller('PrefsCtrl', function PrefsCtrl($scope, timerService) {
$scope.timer = timerService.timer;
})
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="myApp">
<div ng-controller="PrefsCtrl">
<button ng-click="timer.startTimer()">Click to Start or Reset Timer</button>
<div>{{timer.autoRefreshInSec}}</div>
</div>
</div>
note(1), this is shorcut for
stopTimer: function(timer) {
$timeout.cancel(timer)
}
This is a javascript binding issue.
Try:
var somedata; // now this can be referenced in the angular.bind call
somedata = {
onTimeout: angular.bind(somedata, function () {
this.autoRefreshInSec--;
if (this.autoRefreshInSec <= 0) {
this.autoRefreshInSec = this.autoRefreshInterval;
this.doRefresh();
}
this.myTimeout = $timeout(this.onTimeout, 1000);
})
}
which will ensure the this at the time that function is called is "bound" to somedata inside the callback.