AngularJS, the variables doesn't change in the view - javascript

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/)!

Related

AngularJS 2-way-binding not updating

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>

AngularJS typeahead otions not up to date

I'm writing a directive wrapper around a typeahead input. This directive listens for changes on a link and get's new data + options for the typeahead.
I can simply simulate this behaviour with a $timeout and demonstrated it in this plnkr.co.
JS
app.controller('sample', function($scope, $timeout) {
$scope.options = ['1800', '1900', '2100'];
// Simulate some latency
$timeout(function () {
$scope.options.push('1850');
}, 4000);
});
HTML
<div>
<input type="text" ng-model="optionValue" typeahead="opt for opt in options | filter:$viewValue">
</div>
If you start typing '18' in the input field it shows 1800 as expected. But when 1850 get's added after an amount of time, the selectable options from typeahead are not being updated.
-- FYI my real live directive looks like this --
$scope.$watch($interpolate(url), function (newUrl) {
$http.get(newUrl).then(function (response) {
$scope.options = response;
});
});
I tried to use typeahead="opt for opt in getData()" but this doesn't work because the interpolated value is not yet up to date. It's always one value behind.
Seems like an issue to post on AngularUI Bootstrap website. Matches are getting selected on every keystroke but they don't get updated if you change the underlying data between keystrokes. I don't see any work-around for this, except maybe triggering the appropriate key event handler on the input manually (when you change the collection).
If someone interested in the solution, here is how I solved it at the moment. I'm not happy with the end result, please provide me some feedback :-).
Plunkr
Check out updated-bootstrap.js, I had to add the following in order to make it work:
A custom attribute that'll be use for the $watchCollection
var customOptions = attrs.typeaheadCustomOptions || '';
In the function where it gets the matches I've added a watch if customOptions is provided:
if (customOptions) {
originalScope.$watchCollection(customOptions, function (matches) {
resetMatches();
updateMatches(matches);
});
}
And that was basically it :-), the updateMatches is just an abstraction of existing code. It's not being used by me and the manual update.
var updateMatches = function(matches) {
for (var i = 0; i < matches.length; i++) {
locals[parserResult.itemName] = matches[i];
scope.matches.push({
id: getMatchId(i),
label: parserResult.viewMapper(scope, locals),
model: matches[i]
});
}
scope.query = modelCtrl.$viewValue;
};
Opened issue on github

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

Angularjs watch for change in parent scope

I'm writing a directive and I need to watch the parent scope for a change. Not sure if I'm doing this the preferred way, but its not working with the following code:
scope.$watch(scope.$parent.data.overlaytype,function() {
console.log("Change Detected...");
})
This it logged on window load, but never again, even when overlaytype is changed.
How can I watch overlaytype for a change?
Edit: here is the entire Directive. Not entirely sure why I'm getting a child scope
/* Center overlays vertically directive */
aw.directive('center',function($window){
return {
restrict : "A",
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
var watchForChange = function() {
return scope.$parent.data.overlaytype;
}
scope.$watch(watchForChange,function() {
$window.setTimeout(function() {
resize();
}, 1);
})
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
If you want to watch a property of a parent scope you can use $watch method from the parent scope.
//intead of $scope.$watch(...)
$scope.$parent.$watch('property', function(value){/* ... */});
EDIT 2016:
The above should work just fine, but it's not really a clean design. Try to use a directive or a component instead and declare its dependencies as bindings. This should lead to better performance and cleaner design.
I would suggest you to use the $broadcast between controller to perform this, which seems to be more the angular way of communication between parent/child controllers
The concept is simple, you watch the value in the parent controller, then, when a modification occurs, you can broadcast it and catch it in the child controller
Here's a fiddle demonstrating it : http://jsfiddle.net/DotDotDot/f733J/
The part in the parent controller looks like that :
$scope.$watch('overlaytype', function(newVal, oldVal){
if(newVal!=oldVal)
$scope.$broadcast('overlaychange',{"val":newVal})
});
and in the child controller :
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//any other action can be perfomed here
});
Good point with this solution, if you want to watch the modification in another child controller, you can just catch the same event
Have fun
Edit : I didn't see you last edit, but my solution works also for the directive, I updated the previous fiddle ( http://jsfiddle.net/DotDotDot/f733J/1/ )
I modified your directive to force it to create a child scope and create a controller :
directive('center',function($window){
return {
restrict : "A",
scope:true,
controller:function($scope){
$scope.overlayChanged={"isChanged":"No","value":""};
$scope.$on('overlaychange', function(event, args){
console.log("change detected")
//whatever you need to do
});
},
link : function(scope,elem,attrs){
var resize = function() {
var winHeight = $window.innerHeight - 90,
overlayHeight = elem[0].offsetHeight,
diff = (winHeight - overlayHeight) / 2;
elem.css('top',diff+"px");
};
angular.element($window).bind('resize',function(e){
console.log(scope.$parent.data.overlaytype)
resize();
});
}
};
});
You should have the data property on your child scope, scopes use prototypal inheritance between parent and child scopes.
Also, the first argument the $watch method expects is an expression or a function to evaluate and not a value from a variable., So you should send that instead.
If you're looking for watching a parent scope variable inside a child scope, you can add true as second argument on your $watch. This will trigger your watch every time your object is modified
$scope.$watch("searchContext", function (ctx) {
...
}, true);
Alright that took me a while here's my two cents, I do like the event option too though:
Updated fiddle
http://jsfiddle.net/enU5S/1/
The HTML
<div ng-app="myApp" ng-controller="MyCtrl">
<input type="text" model="model.someProperty"/>
<div awesome-sauce some-data="model.someProperty"></div>
</div>
The JS
angular.module("myApp", []).directive('awesomeSauce',function($window){
return {
restrict : "A",
template: "<div>Ch-ch-ch-changes: {{count}} {{someData}}</div>",
scope: {someData:"="},
link : function(scope,elem,attrs){
scope.count=0;
scope.$watch("someData",function() {
scope.count++;
})
}
};
}).controller("MyCtrl", function($scope){
$scope.model = {someProperty: "something here");
});
What I'm showing here is you can have a variable that has two way binding from the child and the parent but doesn't require that the child reach up to it's parent to get a property. The tendency to reach up for things can get crazy if you add a new parent above the directive.
If you type in the box it will update the model on the controller, this in turn is bound to the property on the directive so it will update in the directive. Within the directives link function it has a watch setup so anytime the scope variable changes it increments a counter.
See more on isolate scope and the differences between using = # or & here: http://www.egghead.io/

jquery return value of input to var

I'm trying to make a variable equal the value of the text box. I have the text box value being set to a variable and returned as an alert (for now) but I can't figure out how to call that variable from other functions.
$('#max_char').keyup(function () {
var max_char = $(this).val();
alert(max_char + ' Handler for .keyup() called.');
});
var count_options = {
'maxCharacterSize': max_char,
'originalStyle': 'originalDisplayInfo',
'warningStyle': 'warningDisplayInfo',
'warningNumber': 40,
'displayFormat': '#input Characters | #left Characters Left | #words Words'
};
$('#textinput').textareaCount(count_options);
});
HTML
<textarea cols="68" rows="21" name="textinput" id="textinput"></textarea><br/>
<input type="textbox" id="max_char" name="max_char" value="-1" /> Max Characters <br/>
Any help would be great. Trying to add the var max_char to the maxCharacterSize of count_options
All you need to do is declare max_char in a higher scope, i.e. outside of the keyup function:
var max_char;
$('#max_char').keyup(function () {
max_char = +$(this).val();
alert(max_char + ' Handler for .keyup() called.');
});
Also note that I put a + in front of $(this).val() to convert it from a string into a number, since "1" + 1 == "11".
Update:
The reason the textareaCount plugin isn't working is because it is initialised once, on document ready. At this time, max_char is nothing because the user hasn't typed anything yet.
You'd have to either reconfigure or re-initialise the plugin on every keyup to get the effect you're after. Unfortunately the plugin doesn't document an easy way to do this. After digging through the plugin's source code, I think there are only 3 events it binds that you need to revert, before you can simply re-initialize it again. Try this out:
var count_options = {
'maxCharacterSize': 100, // Just some default value
'originalStyle': 'originalDisplayInfo',
'warningStyle': 'warningDisplayInfo',
'warningNumber': 40,
'displayFormat': '#input Characters | #left Characters Left | #words Words'
};
// Initialise the plugin on document ready
$('#textinput').textareaCount(count_options);
$('#max_char').keyup(function () {
var max_char = +$(this).val();
count_options.maxCharacterSize = max_char;
// Unbind the 3 events the plugin uses before initialising it
$('#textinput')
.next('.charleft').remove().end()
.unbind('keyup').unbind('mouseover').unbind('paste')
.textareaCount(count_options);
});
If I understand you correctly, if you declare the var within the global scope of javascript
Or if you directly access the input with
$("#max_char").val()
parseInt($('#max_char').val())

Categories

Resources