Dynamic Updates and AngularJS - javascript

I have this...
<script> var num = 22;</script>
Then inside of the controller block...
<span>{{somenumber}}</span>
In the controller...
$scope.somenumber = num;
This all works as expected.
How would I go about having it all update if the value of the num variable changes? So, I'd have some code (from socket.io or AJAX) change num to 65. Right now, it still says 22.

I'd take a look at this
num is a primitive type (Number). So When you're assigning it to the $scope you're copying it. What you need to do is reference it instead. I'd fix it the following way.
<script>var value = {num: 22} </script>
$scope.value = value;
<span> {{value.num}} </span>
If your ajax call is not through $http.(outside angular - wherever you set value.num) you'll need to invoke a digest cycle. The easiest way to do that is in an angular service like $timeout.
Think of the scope as
$scopeHAS model instead of $scopeAS model

You could use $watch followed by $apply:
Controller
$scope.somenumber = num;
$scope.$watch(function() {
return num;
}, function(newValue, oldValue) {
$scope.somenumber = newValue;
});
// fake external change to the 'num' variable
setTimeout(function() {
num = 33;
$scope.$apply();
}, 3000);
Here's a working example: http://plnkr.co/edit/rL20lyI1SgS6keFbckJp?p=preview
If your external change is happening outside the scope of a single controller, I would use $rootScope inside a run callback:
angular.module('exampleApp', []).run(function($rootScope) {
// same as above, just with $rootScope
});

Related

AngularJS two controllers with shared model, controller 2 not seeing change to model

Hitting the ceiling of my Angular knowledge and I have been going around in circles on this.
Basically I have video player and chapter list directives, each with a controller. The controllers use the same model service which looks like this:
.service('VideoPlayerModel', function(){
var model = this;
model.chapters = {
chapterPos: 0,
targetChapter:null,
data: []
},
model.getVideoData = function() {
return model.videoData;
};
model.setVideoData = function(vData){
...
...
...
};
});
In the video player controller as the time of the player updates it finds the needed chapter data and updates the model.chapters data like this:
updateChapter: function(currentTime){
var chapters = VideoPlayerModel.chapters;
var chaptersCtrl = videoPlayerCtrl.chapters;
if (chapters.nextChapter.start <= currentTime) {
chapters.chapterPos = chapters.chapterPos + 1;
chaptersCtrl.setChapter(); //This finds and sets the Target Chapter
}
},
After setChapter runs I call console.log(VideoPlayerModel.chapters) and I can see the data model has updated with a result like this:
Object {chapterPos: 1, targetChapter: Object, data: Array[6], nextChapter: Object}
However the watch in the ChapterListCtrl doesn't fire and any of the onscreen items displaying the ChapterPos still show just the initial val of 0.
The controller looks like this:
.controller("ChapterListCtrl", ['$scope', 'VideoPlayerModel', function($scope, VideoPlayerModel) {
$scope.chapters = VideoPlayerModel.chapters;
$scope.$watch(function() { return VideoPlayerModel.chapters; }, function(newValue, oldValue){
$scope.chapters = newValue;
console.log("A Change"); // Only runs at initialisation.
});
}])
I have tried different ways and ended up with this, not sure if I am in the complete wrong direction now. Can anyone please help?
You don't need to use $watch, $broadcast or $on. This is best solved by regular JavaScript thinking.
Your problem is $scope.chapters = newValue; That is where you break the binding that your controllers use by introducing a new object unrelated to your service.
What you should to instead is to think about your service model.chapters = {..} and say hey! This is THE ONE object that I will use. And if I want to change the data in this object anywhere, I will switch the data inside the object and NOT assign a new object to the reference I use.
To do this I use the following methods:
transferDataList = function (from, to) {
/*
http://stackoverflow.com/questions/1232040/empty-an-array-in-javascript
*/
to.length = 0;
for (var i = 0; i < from.length; i++) { to.push(from[i]); }
};
transferDataMap = function (from, to) {
/*
http://stackoverflow.com/questions/684575/how-to-quickly-clear-a-javascript-object
*/
var member;
for (member in to) { delete to[member]; }
for (member in from) { to[member] = from[member]; }
};
And when I want to change the data in my object I DON'T do:
$scope.chapters = newValue;
Instead I do:
transferDataMap(newValue, $scope.chapters);
Or:
transferDataList(newValue, $scope.chapters);
This way you will keep your binding and your Angular interfaces will always be updated.
You can use $broadcast() and $on() function to achieve your requirement.
$broadcast() will flush an event to all it's child controller. So, you can $broadcast() an event with your new value to all controllers when you set a new value to your shared model.
Add a broadcast method in your shared service.
model.setVideoData = function(vData){
UpdateYourModel();
// Inform that your model is updated
$rootScope.$broadcast('modelUpdated');
}
And now you can add a listener for the event modelUpdated in all your controllers.
$scope.$on('modelUpdated', function () {
$scope.controllerModel = VideoPlayerModel.getVideoData(); // Update your controller model
}
And also, inject $rootScope to your service,
.service("VideoPlayerModel", ["$rootScope", function($rootScope){
// define your service here.
}] );
That's all !!!
I hope this will help you.
Try changing your watcher to:
$scope.$watch('chapters', function(newValue, oldValue){
$scope.chapters = newValue;
console.log("A Change"); // Only runs at initialisation.
});
Alternatively if that doesn't achieve what you want, you can enable a deep watch by passing the third argument:
$scope.$watch('chapters', function(newValue, oldValue){
$scope.chapters = newValue;
console.log("A Change"); // Only runs at initialisation.
}, true);
Your watcher doesn't fire because it always returns the same chapters which Angular considers as not-changed because it checks by reference. Your watcher can also be refactored as:
$scope.$watch(function() { return VideoPlayerModel.chapters.length; }, function(newValue, oldValue){
$scope.chapters = newValue;
console.log("A Change"); // Only runs at initialisation.
});

Updating a 'this' value in a service via a function

I'm quite new to Angular and am trying to understand how everything works. I've been poking around and couldn't find any information on how to do this. So, I've got a service that defines
this.totalCount = 0;
In my controller, my get request retrieves some emails and then executes a function called addMessage for each message it retrieves. The addMessage function is in my service.
The function in my service looks like this:
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
}
Basically, I am trying to increment this.totalCount each time this function is executed so that it will update and then can be displayed in the view. I have it displaying in the view currently, however its number always remains 0.
I've tried the following:
1.
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
this.totalCount++;
}
2.
var count = this.totalcount
this.addMessage = function (messageObj) {
this.messagesList.push(messageObj);
count++; //and then attempted to display this value in the view but with no luck
}
Any tips would be greatly appreciated.
try this:
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
}
I assume that you're binding the var this way in your controller and your view
Service :
this.totalCount = 0;
this.totalCount++;
Controller :
$scope.totalCount = service.totalCount;
view :
{{totalCount}}
And if you're actually doing it like this, you should face this kind of trouble.
The main problem is that totalCount is a primitive var and doing this.totalCount++ will break the reference. If you want to keep some var you should bind it as a sub-object.
This way :
Service :
this.utils = {};
this.utils.totalCount = 0;
this.utils.totalCount++;
Controller :
//This is the most important part. You bind an object. Then even if you loose the totalCount reference, your object will keep its own reference.
$scope.myServiceUtils = service.utils;
View :
{{myServiceUtils.totalCount}}
Actually in service (it's a matter of taste) i prefer a lot to use the object syntax instead of this (as "this" can be confusing)
This way :
var service = {};
service.utils.totalCount = 0;
service.addItem = function(){
...
}
return service;
Hope that was your issue.
You pass argument to another function which has different scope than your service. It is trick with assigning current object to variable, which is visible from function.
var that = this;
this.addMessage = function (messageObj) {
that.messagesList.push(messageObj);
that.totalCount++;
}
Should work.
So you assign that variable with current object, which is visible in inner function scope.
In a function addMessage body, this refers to function scope which is new, and there is no compiler error, but messagesList is a null object and totalCount is incremented, but after program leave function, it's not visible in service, because it is in a function scope which isn't assigned to any variable.
To update service variable as it changes in your controller, use $watch.
$scope.$watch(function() {
return messagesService.totalCount;
}, function(new,old){
$scope.totalmessagecount = messagesService.totalCount;
});
First parameter of $watch if function which return observed for change element. Another is standard function to perform operation after update.

Value not updating on the HTML using Angular

I have a controller in which a value gets randomly generated
app.controller('detailReadingCtrl',function(){
var value = 0;
$scope.dispValue = 0;
setInterval(function(){
value = Math.floor(Math.random()*1000);
$scope.dispValue = value;
}
});
and my html is
<div>{{dispValue}}</div>
The Value on the html is not getting updated with the changed value. The dispvalue is changing in the controller but it is not updating in the html. I want to see the value change for every second on the screen without the need of refresh.
I tried using $scope.$watch and also $scope.$broadcast both seem to not work. Please let me know if you have any ideas about this.
setInterval doesn't trigger a $digest cycle, instead, use the $interval module:
app.controller('detailReadingCtrl',function($scope, $interval){
var value = 0;
$scope.dispValue = 0;
$interval(function(){
value = Math.floor(Math.random()*1000);
$scope.dispValue = value;
});
});
And you had other errors in the code ($scope wasn't being injected, missing ) at the end of the interval)

AngularJS : databinding in isolated scope of directive, using an object?

I have a quite hard time to build a (maybe non-trivial) directive for an SPA. Basically I need to be able to pour data into the directive from any controller and the directive is supposed to show a live graph.
I've read this post and I would like to use a shared object on the isolated scope of this directive anyway.
So I tried to do smth like this:
Wrapping template:
<div ng-controller="WrappingCtrl">
<timeline-chart d3API="d3API"><timeline-chart>
</div>
In the 'wrapping' controller:
$scope.d3API = {};
$scope.d3API.options = {}; //for d3Config
$scope.d3API.currentValue = 3; //asynchronous!!!
Finally to use the shared object d3API in the directive's link method I tried e.g. this:
//in the directive:
scope: { //nice, but does it help??
d3API: '='
}
and:
var data = [1, 2];
var updateTimeAxis = function() {
var newValue;
if (data.length) {
newValue = (data[data.length - 1] !== scope.d3API.currentValue) ? scope.d3API.currentValue : data[data.length - 1];
data.push(newValue);
} else {
console.warn('problem in updateTimeAxis: no data length');
}
};
To gain some simplicity for this question I've created a fiddle, note, that none of both are working:
http://jsfiddle.net/MalteFab/rp55vjc8/3/
http://jsfiddle.net/MalteFab/rp55vjc8/5/
The value in the directive's template is not updated - what am I doing wrong? Any help is appreciated.
Your fiddle mostly works, you just need to update your controller to use $timeout:
app.controller('anyCtrl', function($scope, $timeout) {
show('anyCtrl');
$scope.bound = {};
$timeout(function() {
$scope.bound.says = 'hello';
}, 200);
});
Forked fiddle: http://jsfiddle.net/wvt1f1zt/
Otherwise no digest occurs so angular doesn't know something changed. Based on what you're actual problem is, I'm assuming you're not using timeout vs $timeout, but if your coding style is to intermix angular with "normal" javascript, you may be running into the same kind of scenario.
A good article for reference for telling angular about what your doing is here: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html

AngularJS - Creating a compile function

I'm trying to create a custom compile function, to make it easier to dynamically add HTML to a page.
The argument htmlStr is the incoming HTML to compile. The argument value is a variable that can be added to the scope. The argument compiledHTMLFunc is a function that will be executed with the compiled object. Here's my code:
function compileHTML (htmlStr, value, compiledHTMLFunc)
{
var $injector = angular.injector (["ng", "angularApp"]);
$injector.invoke (function ($rootScope, $compile)
{
$rootScope.value = value;
var obj = angular.element (htmlStr);
var obj2 = $compile (obj)($rootScope);
if (compiledHTMLFunc != null)
compiledHTMLFunc (obj2);
});
}
Here's how I use the function:
compileHTML ("<button class = \"btn btn-primary\">{{ value }}</button>", "Ok", function (element)
{
$(document.body).append (element);
});
Whenever I try to compile the following HTML, the inline {{ value }} doesn't get compiled. Even if I simply change it to {{ 1+1 }}. Why is this?
Update: I dunno why I didn't create a fiddle earlier, here's an example: http://jsbin.com/vuxazuzu/1/edit
The problem appears to be pretty simple. Since you invoke compiler from outside of angular digest cycle you have to invoke it manually to boost the process, for example by wrapping compiledHTMLFunc into $timeout service call:
function compileHTML (htmlStr, scope, compiledHTMLFunc) {
var $injector = angular.injector(["ng", "angularApp"]);
$injector.invoke(function($rootScope, $compile, $timeout) {
$rootScope = angular.extend($rootScope, scope);
var obj = $compile(htmlStr)($rootScope);
if (compiledHTMLFunc != null) {
$timeout(function() {
compiledHTMLFunc(obj);
});
}
});
}
compileHTML('<button class="btn btn-primary">{{value}}</button>', {value: 'Ok'}, function(element) {
angular.element(document.body).append(element);
});
I also improved your code a little. Note how now compileHTML accepts an object instead of single value. It adds more flexibility, so now you can use multiple values in template.
Demo: http://plnkr.co/edit/IAPhQ9i9aVVBwV9MuAIE?p=preview
And here is your updated demo: http://jsbin.com/vuxazuzu/2/edit

Categories

Resources