angular infinite digest loop from ng-model using getterSetter - javascript

Here is the html:
<select style="width: 100%;" ng-model="vm.orgType" ng-model-options="{getterSetter: true}" ng-options="orgType as orgType.ORGANIZATION_TYPE for orgType in vm.orgTypes">
</select>
and here is the getter/setter function:
function orgType(selectedType) {
if (arguments.length == 0)
return orgType.selectedOrgType || { ORGANIZATION_TYPE: 'Organization Type', ORGANIZATION_TYPE_ID: null };
orgType.selectedOrgType = selectedType;
if (selectedType.ORGANIZATION_TYPE_ID) {
if (vm.registrant.StakeholderOrgs[0])
vm.registrant.StakeholderOrgs[0] = selectedType.ORGANIZATION_TYPE_ID;
else
vm.registrant.StakeholderOrgs.push(selectedType.ORGANIZATION_TYPE_ID);
}
else
vm.registrant.StakeholderOrgs.splice(0);
}
the following line:
return orgType.selectedOrgType || { ORGANIZATION_TYPE: 'Organization Type', ORGANIZATION_TYPE_ID: null };
throws the infinite digest loop error.
Let me explain what I am trying to do here. I need to push the id onto a list if there is a selection made. I realize that I could just do an ng-model on some variable selectedOrgType and then just put my logic in an ng-change. However, I am trying to make a dropdown that does not create any unnecessary model variables. Instead, I was hoping to just put the logic in a getter/setter, that seems more appropriate to me. One of vm.orgTypes is { ORGANIZATION_TYPE: 'Organization Type', ORGANIZATION_TYPE_ID: null }, I was hoping that would be my default value instead I get this digest error, don't understand where it is coming from.

When you add ng-model attribute, angular add internal watch, that check value on every digest loop, and if value changed - run digest again.
In you case you return object literal. In javascript when you compare two literals, even with same structure - you get false
({a:1} == {a:1}) // false
because this really two different object.
So, when you return object literal in your getter, watch check it with previous value, and, as i say above, if you return literal - get false
So you get your error with infinite digest.
For solving you just need return same object.
If you have this object inside array, like
vm.orgTypes=[
{ ORGANIZATION_TYPE: 'Organization Type', ORGANIZATION_TYPE_ID: null }
];
So you just need use it directly:
return orgType.selectedOrgType || orgTypes[0];
Yer another way: just save default falue to varible and use it
var defaultSelect = { ORGANIZATION_TYPE: 'Organization Type', ORGANIZATION_TYPE_ID: null };
....
function orgType(selectedType) {
if (arguments.length == 0)
return orgType.selectedOrgType || defaultSelect;
In this case you would return same element in default case, so avoid infinite digest loot.

Related

How to set a data value after v-if has been resolved

I'm trying to do what I would think is rather simple, but I can't seem to find a built-in way to do it. Basically, I have several divs on a page that are using v-if conditions which are acting as filters for data on the page (think about a table with data that is then filtered by select boxes)
Below is a very simplified example, but basically, I'm wanting to set a variable in my data object once a v-if condition is satisfied. Then, if filters change and make a different v-if condition satisfied it would set the same variable to a different value.
I basically want a value that can be changed based on any filters on my page, as long as I have a way to set that value after any given v-if is satisfied.
I was hoping to be able to simply call a method (with an argument passed) once v-if resolved is possible
var vm = new Vue({
el: "#app",
props: {
},
data: {
showData: 'ABC',
specificData: "here are some specifics",
newValue: ''
}
});
<div id ="app">
<div v-if="showData === 'ABC'">
<!--Here, I want to set newValue to something like 'ROGER' unrelated to ABC-->
ABC
</div>
<div v-if="showData === '123'">
<!--Here, I want to set newValue to something like 'SAM' unrelted to 123-->
123
</div>
</div>
This kind of business logic should not be handled in template, but rather in a computed property. The most basic setup would look like:
data(){
return {
showData: "ABC"
}
},
computed: {
newValue(){
if (this.showData === "ABC") {
return "Some derived value"
}
return ''
}
}
Alternatively, you can use a watcher on showData, and call additional methods when any of the conditions are met.
watch: {
showData(val){
if (val === "ABC") {
this.newValue = "Some derived value"
this.someOtherMethod()
}
// Any other conditions to be checked
// or simply pass the value further to a method where all the checks are done
this.checkValue(val)
}
}

Vue-Form-Generator schema is not reactive to computed properties

I have computed property in my data this.coinPairingOptions that needs to render its radio buttons based on some of the other fields in this schema. I have reduced the amount of code in the file to save space.
data: function () {
return {
schema: {
{model: "symbolPair", type: "radios", label: "Pair with", values:
this.coinPairingOptions, required: true}
},
computed: {
coinPairingOptions() {
console.log("computing coinPairingOptions")
let coin = this.model.symbol.toUpperCase();
let options = [];
if (this.model.exchange === 'Coinbase') {
options = this.getCoinbasePairs
} else if (this.model.exchange === 'Binance') {
options = this.getBinancePairs
} else {
}
console.log(options.get(coin));
return options.get(coin);
},
}
In the dev tools I can see the computed property changing to the correct values however it is not changing in the data. Apparently, this is appropriate behavior, but what is a way around this? I have tried putting {{this.coinPairingOptions}} in the html and it errors because it's a computed property with not value initially.
Any help would be appreciated!
You can't use computed property in data, because data evaluates before the computed properties did.
You can use a watcher to achieve the intended result. Have a look at the documentation, you can add the argument immediate to trigger the callback immediately with the current value of the expression.
Computed properties are already accessible in the template by using {{}}. You don't need to put a this in front of the computed.

Using Filter Function to Find Values Equal to False and Setting to True

In my Angular app I have a function that drills down to an array, and then uses a filter function to pull out values in a new array where "completed" is "false".
This is working as expected. And the way our data is, there is always one object in the array that has the property for "completed" set to "false", so I can target [0] to get to that. So, from there all I need to do is set it to "true". However, for whatever reason, how to accomplish this last step is eluding me.
This is my whole function, and what I've tried thus far:
private completeLastWorkflowStatus() {
let currentService = this.checkDiscipline();
for (let service of this.client.services) {
if (service.service === currentService) {
let targetWorkflow = service.workflow;
let inCompleteWorkflow = targetWorkflow.filter(workflow => workflow.completed === false);
console.log(inCompleteWorkflow);
if (inCompleteWorkflow[0].completed === false) {
inCompleteWorkflow[0].completed === true;
console.log(inCompleteWorkflow[0].completed);
}
}
}
}
For the last console.log listed above, I still get "false" as the value. What am I missing here? How can I set the value of "completed" to "true" for this one object in the array?
inCompleteWorkflow[0].completed === true; is not assignment. Do inCompleteWorkflow[0].completed = true;

angularjs function failing because of extra $$hashKey

I have the below HTML:
<li ng-click="toggleBeep(beep)" ng-class-odd="'gradient-two'"
ng-class-even="'gradient-three'" ng-repeat="beep in beeps">
<span>{{beep.name}}</span>
<label class="bold" ng-show="isSelected(beep)">selected</label>
</li>
JavaScript (AngularJS):
$scope.beeps = $sounds.getAll();
// get stored beep from localStorage
var notification_beep =
angular.fromJson(localStorage.getItem('notification_beep'));
console.log($scope.beeps[0]);
console.log(notification_beep);
// handle change sound on click event
$scope.toggleBeep = function (beep) {
$cbSounds.play(beep.file);
$scope.selected = beep;
localStorage.setItem('notification_beep', angular.toJson(beep));
};
$scope.isSelected = function (beep) {
return $scope.selected === beep;
};
Now, when I click on any li I get the selected label is shown because of the $scope.isSelected function. However, when I try to add this line $scope.selected = notification_beep which is the beep object stored in the localStorage the label is not shown and I get the below return values.
The only difference I could spot is that $$hashkey is present on $scope.beeps[0] while it's not on notification_beep. Could this be the cause? Thanks.
The following comparison:
$scope.selected === beep
Will only return true if the two variables reference the same object.
The following line will create a new object:
var notification_beep = angular.fromJson(localStorage.getItem('notification_beep'));
So it will not reference the same object as $scope.selected.
To clarify, this will return false: { name: 'Beep 1' } === { name: 'Beep 1' }
The simplest solution is to instead compare against a unique primtive of the objects.
For example:
return $scope.selected.name === beep.name;
The $$hashkey property is inserted into the object by ng-repeat and is used to track which object corresponds to which DOM template. The reason it doesn't exist in notification_beep is because angular.toJson removes the property from the object.

AngularJs why is object undefined on ng-disabled function?

VIEW:
I have a rows repeating , with a save button on each row to save each object individually. I want this button to be disabled if no changes have been made.
<tr ng-repeat="option in options | filter:search">
<a ng-click="save(option)" ng-disabled="isUnchanged(option)">Save</a>
</tr>
CONTOLLER:
So I pass the option object to the function, I get its index position in the array. Then compare this 'option' object to its original self in apiKeyOptions[index] which is injected as a service.
angular.module('PartOfApp')
.controller('PartOfAppCtrl', function( $scope, ... apiKeyOptions) {
$scope.options = apiKeyOptions;
$scope.isUnchanged = function(option) {
var index = $scope.options.indexOf(option);
//compare object to the original
if(option.value == apiKeyOptions[index].value && apiKeyOptions[index].setting == option.setting){
//then no changes have been made to this
return true;
}else{
return false;
}
For some reason I get a console of 100's of errors when any data is changed, saying that the apiKeyOptions[index].value and apiKeyOptions[index].setting are undefind.
The app works perfectly as it should returning true if they are the same but still throws a
TypeError: Cannot read property 'value' of undefined
on apiKeyOptions[index]
if I console.log(apiKeyOptions[index].value) I get no undefined values and all log correctly.
Im guessing Im breaking some angular rules, if anyone could help that would be great.
apiKeyOptions overview:
apiKeyOptions is an array of up to 50 objects
each object is in the form
{
defaultValue: boolean,
description: null,
name: String,
setting: "Default" or Boolean,
value: Boolean
}
Added after comment below:
If I add-
console.log(index);
console.log(apiKeyOptions[index]);
to the function $scope.isUnchanged, I get the expected results
example :
13
Object {name: "LOREM IPSUM", description: null, defaultValue: false, setting: "default", value: falseā€¦}
So index is not always -1. The reason I pass the object to the function and not $index is because of the filter | search so the index will change depending on the search.
FIXED
As shown in the answer below . I was getting a index = -1 error but its was buried in 100's of CORRECT log outputs.
Oddly this did not stop the app from working and I will need to have a deeper look into how ng-disabled is bound to a value. To fix I simply replaced the indexOf with
for (var i = 0; i< $scope.options.length; i++ ){
if($scope.options[i].name == option.name){
var index = i;
}
}
The problem seems to be with the parameter passed to $scope.isUnchanged = function(option) {
Since ng-repeat creates a new scope for each loop, i suspect that the 'option' available to each loop would be a new object and will not have a reference to 'options' array.
<tr ng-repeat="option in options | filter:search">
Therefore your isUnchanged function will receive parameter as a new object and hence below code always returns -1. Because indexOf matches the given argument in the array and since the argument 'option' is an object and doesn't refer(reference comparison) the same element of array hence no match will found. i.e var a = {id:1};var b = [a]; b.indexOf({id:1}) === -1; b.indexOf(a) === 0;
var index = $scope.options.indexOf(option);//always be -1 in your case
// therefore apiKeyOptions[index] will always be undefined
As a workaround you should pass $index to isUnchanged from the view.

Categories