AngularJS 2-way-binding not updating - javascript

I recently tried out angularJS together with this range-slider-directive:
https://github.com/supertorio/angular-rangeslider-directive
It worked fine using the described while I am using the data-model only inside my HTML-page. But when I am trying to use the model in the correspnding controller, I didn't get the actual value but the initial value I set during initialization in the controller.
controllers.js
app.controller('modelViewCtrl',['$scope','$http','$routeParams', function($scope,$http,$routeParams) {
$scope.modelId = $routeParams.modelId;
$scope.timeIntervalStart = new Date().getTime() - (1000*60*60*24*30);
$scope.timeIntervalEnd = new Date(1452240488000).getTime();
$scope.initialIntervalStart = 1452240488000 - (1000*60*60*24*30);
$scope.initialIntervalEnd = 1452240488000;
$scope.updateInterval = function () {
console.log("update: " + $scope.initialIntervalEnd);
$scope.chosenIntervalStart = new Date($scope.initialIntervalStart);
$scope.chosenIntervalEnd = new Date($scope.initialIntervalEnd);
}
$scope.updateInterval();
}])
html
<div class="panel-heading">
{{chosenIntervalStart}}
<div range-slider
floor={{timeIntervalStart}}
step=86400000
ceiling={{timeIntervalEnd}}
ng-model-low="initialIntervalStart"
ng-model-high="initialIntervalEnd"></div>
{{chosenIntervalEnd}}
</div>
With this I am trying to get a slide which slides between date in Milliseconds with daily steps. I am parsing the changed values in a new Date() object and want to print it out.
The problem is, that I always get only the initialIntervalStart / End values instead of the actual content from the slider.
But when I am using {{initialIntervalEnd}} instead of {{chosenIntervalEnd}}, I get changing values when I change my slider. So it is updating only on 1 part of the 2-way-data-binding.
I tried to update the controller with
$scope.$apply(function() {
//code to update data
});
and
the same with $digest but it didn't worked.
I also tried to use a watcher on the new variable in my controller but with no results as well:
$scope.$watch('initialIntervalStart', function(oldVal,newVal) {
//was only once applied, while initial loading the page
}
When I was using $digest and $apply, I only got errors from my browser.
Do you know how I can force this 2-way-binding?
Kind regards,
Deleadon

You should wrap your timeIntervalStart in an object so that angular can watch it as it is supposed.
Remember that you should not bind models to primitives directly when you need them to be updated and watched. Depending on the directive behavior you may not need this, but to avoid headaches when you expect 2 way binding to work correctly, wrap primitives in an object inside the scope.
$scope.timer = {
timeIntervalStart: new Date().getTime() - (1000*60*60*24*30),
timeIntervalEnd: new Date(1452240488000).getTime(),
initialIntervalStart: 1452240488000 - (1000*60*60*24*30),
initialIntervalEnd: 1452240488000
};
$scope.updateInterval = function () {
console.log("update: " + $scope.initialIntervalEnd);
$scope.timer.chosenIntervalStart = new Date($scope.timer.initialIntervalStart);
$scope.timer.chosenIntervalEnd = new Date($scope.timer.initialIntervalEnd);
};
And the html
<div class="panel-heading">
{{timer.chosenIntervalStart}}
<div range-slider
floor={{timer.timeIntervalStart}}
step=86400000
ceiling={{timer.timeIntervalEnd}}
ng-model-low="timer.initialIntervalStart"
ng-model-high="timer.initialIntervalEnd"></div>
{{timer.chosenIntervalEnd}}
</div>

Related

AngularJS, the variables doesn't change in the view

I've created this simple calculation where the user inserts his levels and by that we get the combat calculation. But the problem is, the view never change, because the variables are somehow static.
Please find jsfiddle for code.
Code link :jsfiddle
There are a few issues with your code.
First using a model for each input element does not tell angular that you wish to capture the change events, in other words that you wish angular get notified when these element value has been changed. That's why you need to use the ng-change directive, which role is to bind the new values to the DOM element.
The second issue is that angular does not know when the model values have been changed, until you do not tell to watch them.
Taking into account the above consideration this is how the refactored code should look like:
var app= angular.module("cmbtCalc", []);
app.controller('CombatCalculatorController', function($scope){
$scope.result = 0;
$scope.ALvl = 1 ;
$scope.SLvl = 1 ;
$scope.MLvl = 1 ;
$scope.RLvl = 1 ;
$scope.HLvl = 10 ;
$scope.DLvl = 1 ;
$scope.PLvl = 1 ;
function RangeMage (real){
return 0.325 * (Math.floor( real/ 2)+real);
};
$scope.RealRange=RangeMage($scope.RLvl);
$scope.RealMage=RangeMage($scope.MLvl);
['SLvl', 'ALvl', 'MLvl', 'RLvl', 'HLvl', 'DLvl', 'PLvl'].forEach(function(val) {
$scope.$watch(val, function(newValue, oldValue) {
$scope.melee = 0.325 * ($scope.ALvl + $scope.SLvl);
$scope.base = 0.25 * ( $scope.DLvl + $scope.HLvl + Math.floor ($scope.PLvl / 2 ));
})
});
$scope.calculatecmbt = function (){
$scope.result = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.result;
};
});
And inside your html you have to define a model for a ng-change directive:
<h2 ng-model="RealRang" ng-change="calculatecmbt">
{{result}}
{{calculatecmbt() | number}}
</h2>
And here is the working code: https://jsfiddle.net/tvbjscp9/5/
I think you does not invoke the function JS FIDDLE
You may have use function invoke on input changed or else use watchers for invoke functions
$scope.$watch('myVar', function(newValue, oldValue) {// You can invoke your custom functions here
});
I guess you will need to call calculatecmbt on change of value and save the output in scope variable that you bind on UI.
<p>Insert your Prayer Level <input type="number" ng-model="PLvl" ng-change="calculatecmbt()"></p>
{{outputVal}}
Controller code
$scope.outputVal = Math.max($scope.melee, $scope.RealRange, $scope.RealMage) + $scope.base;
return $scope.outputVal;
};
I made it work, wasn't passing the function values. Here's the working code :)asdasdasdas
[Working jsfiddle]( https://jsfiddle.net/tvbjscp9/8/)!

Angular directive template css class change delayed

Code snippet from my angular directive class:
if (currentIndex < newMessages.length) {
scope.message = newMessages[currentIndex];
} else {
scope.message = newMessages[0];
}
$timeout(function () {
var msgText = $('#msg-text');
console.log("xx:" + msgText.attr('class'));
// has-image no-desc no-desc-remove
});
When I change the scope.message, angular refreshes the template, and $timeout ensures that its containing function is called after the template is updated with new elements, etc. Right?
It seems it's working, but the problem is, I got classes like "has-image no-desc no-desc-remove". Later it's changed to "has-image".
How can I catch and event, where the classes are fully updated? Because This is no good for my code logic.
How can I catch and event, where the classes are fully updated? Because This is no good for my code logic.
By you code and requirement, you need to know the moment when your $scope.message.imageURL and $scope.message.Description updated. For the most basic way you can go with $scope.$watchGroup()
Example of $watchGroup. (Here I put the condition for both has some value you can adjust the condition in the way you wanted)
$scope.$watchGroup(['message.imageURL', 'message.Description'], function (newValues, oldValues){
// newValues[0] => is => $scope.message.imageURL
// newValues[1] => is => $scope.message.Description
// case of both having some value
if(newValues[0] && newValues[1]){
console.log('things has been changed');
}
});
Turns out I had angular-animate.js included in my html, and it automatically does "Class and ngClass animation hooks". More here https://docs.angularjs.org/guide/animations. Disabled it where needed with $animate.enabled(false, myElement);

Angular dynamic ng-src function results in "10 $digest() iterations reached" error

I recently inherited an asp.net project that uses Angular, which is very new to me, so I apologize in advance for any rudimentary questions or assumptions.
The markup / js below results in an endless number of the following error:
10 $digest() iterations reached. Aborting!
Angular version 1.2.27
I have the following markup (showing only relevant parts for brevity).
<div id="RecentContentGrid" ng-controller="RecentContentCtrl" ng-cloak>
<ul>
<li ng-repeat="item in items" ng-class="item.contentType.toLowerCase() && getItemClass(item)" ng-switch on="item.contentType">
<a href="{{item.url}}" class="content clearfix" title="{{item.displayName}}" ng-switch-default>
<img ng-src="{{getThumbUrlBySize(item, 320)}}?mh=320" />
</a>
</li>
</ul>
</div>
My issue is with the "ng-src="{{getThumbUrlBySize(item, 320)}}" part. This calls a method in the controller, which in turn calls a web service to get a image based on the specified height:
$scope.getThumbUrlBySize = function(item, size){
VideoThumbnail.query({ embedCode : item.embedCode, maxHeight: size }, function (data) {
return data.Content;
});
}
The controller also has the following watch methods:
// Watch Methods
$scope.$watch('params.category', function (newVal, oldVal) {
if (typeof(newVal) == 'string') {
$scope.params.perPage = $scope.total_items;
}
$scope.items = [];
});
$scope.$watchCollection('params', function () {
var items = [];
$q.all(_.compact([fetchArticles(), fetchVideos()])).then(function (data) {
items = _.flatten(data);
if (items.length == $scope.total_items) {
items = $filter('orderBy')(items, 'CreatedAt').reverse();
if (typeof(ad_content) != 'undefined' && ad_content.length > 0 && $scope.ads_served == false) {
items = injectAds(items);
}
for (i = 0; i < items.length; i++) {
items[i].cssClass = "block-" + (i + 1);
}
// Append scope items
$scope.items = $scope.items.concat(items);
}
else {
$scope.messages.push("No more items");
}
});
});
My question is how do I get a dynamic image url based on the specific item property and the passed in value for the size? As I mentioned, Angular is very new to me, so I'd appreciate specific details.
Oh, and I should that that the controller is used for many parts of the site, and that's why the size is passed in on the specific module, rather than at the scope level. The maxHeight variable will change based on where this module is used.
Thank you very much.
There are a couple of issues with your code I can see:
the function getThumbUrlBySize does not return anything. Therefore the markup {{getThumbUrlBySize(item, 320)}}?mh=320 fails to interpolate, leaving img tags with empty src attribute.
VideoThumbnail.query seems to be asynchronous. Even if it returned a Promise object, the markup wouldn't be interpolated with the resolved value.
The VideoThumbnail.query's callback does not actually do anything with the value it's passed (assuming that the method itself doesn't do anything with the value returned from its callback - which is unlikely)
None of these problems seems to cause an infinite $digest loop (from the code you've posted I'd suspect the injectAds function), however they prevent your code from working properly ;)
The easiest way I can imagine right now is to replace the for loop in $watchCollection handler with the following:
angular.forEach(items, function(item, i) {
item.cssClass = "block-" + (i + 1); // this comes from the original for loop
VideoThumbnail.query({ embedCode : item.embedCode, maxHeight: 320 }, function (data) {
item.thumbnail = data.Content + "?mh=320";
/*
VideoThumbnail.query accepts a callback instead of returning a Promise,
so we have to tell AngularJS to "refresh" when the asynchronous
job is done:
*/
$scope.$apply();
});
});
and the img markup:
<img ng-src="{{item.thumbnail}}" />
I wouldn't call this solution perfect, but it should work :)

angular ng-click inside ng-repeat to update $scope and then use $apply to update dom

I'm using angular 1.2
ng-repeat creates divs that also contain ng-click
ng-click updates $scope when clicked
the change in $scope is reflected in ng-repeat using $apply
It works ... but I get an error when I click and I think I am applying $apply incorrectly
here is my jsfiddle link
function appcontrol ($scope, $log) {
// declare $scope vars
$scope.currentlist = [];
for (var i = 0; i < 10; i++) {
$scope.currentlist[i] = {'key':i, 'value':i};
}
$scope.extra = 'My Extra';
$scope.anotherextra = 'Another Extra';
// click handler
$scope.handleCellClick = function(cellnumber){
$log.log(cellnumber + ' clicked');
// update the $scope property
$scope.currentlist[cellnumber].value = 'AAA';
// push out the new change to the dom
$scope.$apply();
// trigger other stuff
}
// click handler
$scope.handleExtraClick = function(arg){
$log.log('extra clicked ', arg);
// update the $scope property
if (arg=='My Extra') $scope.extra = 'AAA';
if (arg=='Another Extra') $scope.anotherextra = 'AAA';
// push out the new change to the dom
$scope.$apply();
// trigger other stuff
}
}
and html
<div ng-controller="appcontrol">
<div ng-repeat="item in currentlist" id="cell{{item.value}}" ng-model="item.value" class="cell" ng-click="handleCellClick(item.key)">{{item.value}}</div>
<div id="cell{{extra}}" ng-click="handleExtraClick(extra)">{{extra}}</div>
<div id="cell{{anotherextra}}" ng-click="handleExtraClick(anotherextra)">{{anotherextra}}</div>
</div>
Angular already call $apply for you when you use ng-click. If you take out all the $scope.$apply() in your code, the error won't show up, and it would work exactly the same. Updated fiddle
You don't need $scope.$apply() way you are using it. AngularJS loads the data in $scope automatically. $apply() is used to execute an expression in angular from outside of the angular framework. Also, I would use this function very sparely as it slows your performance.
For example, you would use $apply() for data changed outside of angular like this:
//explicitly auto adjust width and height when user changes size
$scope.$apply( function(){
$scope.width = $window.innerWidth;
$scope.height = $window.innerHeight;
});
Checkout more here--> https://docs.angularjs.org/api/ng/type/$rootScope.Scope#$apply

Is it possible to defer a computed observable until certain UI elements become visible?

I have a computed observable in my model which looks like this:
this.TrainingPlanTemplates = ko.computed(function ()
{
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
I then have a UI template:
<table>
<!-- ko foreach: TrainingPlanTemplates -->
<tr> ... Various columns bound to TrainingPlanTemplates properties ... </tr>
<!-- /ko -->
</table>
The issue is, the HTML template above contains various custom binding handlers and potentially has a large amount of data. Rendering this table is somewhat slow (like 5 seconds or so). This UI uses the jQuery UI tabs control, so I don't even show the data when the page loads. Most users will never even switch to that tab, meaning I'm usually wasting my time binding that data.
Question: Is there a way to defer the binding of a computed observable until I say so, for example, until a certain jQuery tab becomes active?
Ideas:
I got a few ideas from this page. There does exist a deferEvaluation property, however this will only defer the property until something accesses it, which will happen immediately as a hidden HTML table is still bound to the data.
One idea would be to create a new observable property called TrainingPlanTemplatesLoaded, and set that to true when the tab becomes active. Then, create a dependency between TrainingPlanTemplates and TrainingPlanTemplatesLoaded so that when TrainingPlanTemplatesLoaded changes, TrainingPlanTemplates actually loads in the real data.
Any other ideas on the best way to accomplish this?
Yes, just making another observable that you check before doing your computation:
// set to true when you want the computation to run
this.TrainingPlanTemplatesLoaded = ko.observable(false);
this.TrainingPlanTemplates = ko.computed(function ()
{
if (this.TrainingPlanTemplatesLoaded()) {
var workgroups = model.WorkgroupsImpacted();
var areas = model.AreasImpacted();
var plans = model.PrescribedPlan();
$(plans).each(function (i, v)
{
// A bunch of stuff that really slows everything down
});
// ...
}
}, this);
Sure its possible, see my example:
function VM(){
var self = this;
self.FirstName = ko.observable("John");
self.LastName = ko.observable("Smith");
self.canShow = ko.observable(false);
self.FullName = ko.computed(function(){
if (self.canShow()){
return self.FirstName() + " " + self.LastName();
}
});
}
myVm = new VM();
ko.applyBindings(myVm);
// Represents that at some point
// Some function make something happen
setTimeout(function(){
// Let's say we check if an element was visible
// or check anything that we want to know has happened, then:
myVm.canShow(true);
}, 4000);
<p data-bind="text: FirstName"></p>
<p data-bind="text: LastName"></p>
<p data-bind="text: FullName"></p>

Categories

Resources