ng-model won't reflect changes? - javascript

I have built a simple shopping cart sample :
Those 3 lines are created via ng-repeat.
My goal :
I want the yellow part to become red when the relevant quantity ( red arrow) is more than 3.
So here is what I did : (http://jsbin.com/eXOgOpA/4/edit)
<div ng-repeat='item in items'>
<span ng-class='{isMoreThan3: IsMoreThan3()}'>{{item.title }}</span>
<input ng-model='item.quantity'>
...
</div>
Where IsMoreThan3 is a function which :
$scope.IsMoreThan3=function (){return $scope.item.quantity>3;};
Where
.isMoreThan3
{
color:red;
}
but it doesn't work.( calculations are ok , but the color is never red).
Question :
How can I fix my controller code to yield the right value for the model ?
In other words :
How can the controller , know the current item.quantity ?
nb
I know that I can put the logic into the markup But I don't want that. I want the controller to return a true/false value.

Try:
<span ng-class='{isMoreThan3: IsMoreThan3(item)}'>{{item.title }}</span>
JS:
$scope.IsMoreThan3=function (item){
return item.quantity>3;
};
The reason is ng-repeat will create its own scope, accessing $scope.item will not access the current item in the loop

Related

changing model property value is changing the same property in other object

im new to vue , i want user to be able to add some specific social media links to the page and edit some properties like it's text
i have 2 objects in my data , models and defaults
defaults contains selectable option for social media links and their initial values
basically i copy the default value into models and let the user customize the model via inputs
data () {
return {
models : [] ,
defaults : {
twitter : { id : null , placeholder : 'my twitter' , icon : 'twitter' , text : null , 'link' : null } ,
instagram : { id : null , placeholder : 'my instagram' , icon : 'instagram' , text : null , 'link' : null } ,
tiktok : { id : null , placeholder : 'my tiktok' , icon : 'tiktok' , text : null , 'link' : null } ,
} ,
}
} ,
so there a select menu for user to select which social he wants to add to the page
Select :
<ul >
<li v-for="(social, index ) in defaults" :key="index">
<a #click="appendSocial(index)">
{{ index }}
</a>
</li>
</ul>
here is my #click="appendSocial(index)" function
appendSocial(type){
let typedefault = this.defaults[type];
this.models.push(typedefault)
},
and finally i show my models to user and provide an input for editing it's text via v-model
<div v-for="(model, index) in models" v-bind:key="model.id">
<a class="button-preview">
{{ model.text === null ? model.placeholder : model.text }}
</a>
<label>Text</label>
<input type="text" v-model="model.text" :key="index" :placeholder="model.placeholder">
</div>
so here is the problem , for some reason changing the models properties will change the same property in defaults ... and changing defaults properties will change the same property models !!
like if i add a twitter menu to the page and change it's text (model.text) to abc via v-model ... it will also change defaults.twitter.text to abc
to better demonstrate the problem i've added some console log to my appendSocialfunction
appendSocial(type){
let typedefault = this.defaults[type];
console.log(`-------- default object for ${type} -----------`);
console.log(typedefault);
this.addElement(typedefault);
},
here is the result
1 - i've selected a twitter link and added to my models , you can see defaults.twitter.text is null
2 - i've change my model.text (i suppose it would be models[0].text) to abc
3 - i've added another twitter link to my page ... this time defaults.twitter.text is also abc
also changing defaults properties will effect all the models the has been getting their value from that default object
like if i change the defaults.instagram.text to xyz all my models which have got their initial values from defaults.instagram will also change their text to xyz
it's like they are referencing each other , but i haven't passed the value between 2 objects by reference
im not sure what's happening here and how can i prevent this ?
This is because
let typedefault = this.defaults[type];
this.models.push(typedefault)
Is storing the reference to the object into your this.models array. And so if you mutate the element, you're by default changing the base object. A quick and dirty way of doing a deep clone is the following.
let typedefault = this.defaults[type];
let clonedObj = JSON.parse(JSON.stringify(typedefault));
this.models.push(clonedObj)
Note: Lodash library does have a proper deep clone functionality.
https://www.geeksforgeeks.org/lodash-_-clonedeep-method/

Aurelia get value conventer results in view

I would like to get the result of value conventer that filters an array in my view in order to display the number of results found.
<div repeat.for="d of documents|docfilter:query:categories">
<doc-template d.bind="d"></doc-template>
</div>
I neither want to move this logic to my controller (to keep it clean), nor to add crutches like returning some data from the value controller.
What I want:
So, basically I would like something like angular offers:
Like shown here:
ng-repeat="item in filteredItems = (items | filter:keyword)"
or here: ng-repeat="item in items | filter:keyword as filteredItems"
What I get:
Unfortunately, in Aurelia:
d of filteredDocuments = documents|docfilter:query:categories
actually means d of filteredDocuments = documents |docfilter:query:categories, and if I add brackets or as, it won't run (fails with a parser error).
So,
Is there a clean way of getting data out of data-filter in view?
Best regards, Alexander
UPD 1: when I spoke about returning some data from the value controller I meant this:
export class DocfilterValueConverter {
toView(docs, query, categories, objectToPassCount) {
...
objectToPassCount.count = result.length;
...
});
});
UPD 2. Actually, I was wrong about this: d of filteredDocuments = documents |docfilter:query:categories. It does not solve the issue but what this code does is :
1) filteredDocuments = documents |docfilter:query:categories on init
2) d of filteredDocuments which is a repeat over the filtered at the very beginning array
Assuming you have an outer-element, you can stuff the filtered items into an ad-hoc property like this:
<!-- assign the filtered items to the div's "items" property: -->
<div ref="myDiv" items.bind="documents | docfilter : query : categories">
<!-- use the filtered items: -->
<div repeat.for="d of myDiv.items">
<doc-template d.bind="d"></doc-template>
</div>
</div>
I know this isn't exactly what you're looking for but it will do the job. I'm looking into whether it would be helpfull to add a let binding command- something like this: <div let.foo="some binding expression">
Edit
Here's something a bit nicer:
https://gist.run/?id=1847b233d0bfa14e0c6c4df1d7952597
<template>
<ul with.bind="myArray | filter">
<li repeat.for="item of $this">${item}</li>
</ul>
</template>

Update unrelated field when clicking Angular checkbox

I have a list of checkboxes for people, and I need to trigger an event that will display information about each person selected in another area of the view. I am getting the event to run in my controller and updating the array of staff information. However, the view is not updated with this information. I think this is probably some kind of scope issue, but cannot find anything that works. I have tried adding a $watch, my code seems to think that is already running. I have also tried adding a directive, but nothing in there seems to make this work any better. I am very, very new to Angular and do not know where to look for help on this.
My view includes the following:
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<fieldset>
<p data-ng-repeat="person in staffCtrl.persons">
<input type="checkbox" name="selectedPersons" value="{{ physician.StaffNumber }}" data-ng-model="person.isSelected"
data-ng-checked="isSelected(person.StaffNumber)" data-ng-change="staffCtrl.toggleSelection(person.StaffNumber)" />
{{ person.LastName }}, {{ person.FirstName }}<br />
</p>
</fieldset>
</div>
<div data-ng-controller="staffController as staffCtrl">
# of items: <span data-ng-bind="staffCtrl.infoList.length"></span>
<ul>
<li data-ng-repeat="info in staffCtrl.infoList">
<span data-ng-bind="info.staffInfoItem1"></span>
</li>
</ul>
</div>
My controller includes the following:
function getStaffInfo(staffId, date) {
staffService.getStaffInfoById(staffId)
.then(success)
.catch(failed);
function success(data) {
if (!self.infoList.length > 0) {
self.infoList = [];
}
var staffItems = { staffId: staffNumber, info: data };
self.infoList.push(staffItems);
}
function failed(err) {
self.errorMessage = err;
}
}
self.toggleSelection = function toggleSelection(staffId) {
var idx = self.selectedStaff.indexOf(staffId);
// is currently selected
if (idx >= 0) {
self.selectedStaff.splice(idx, 1);
removeInfoForStaff(staffId);
} else {
self.selectedStaff.push(staffId);
getStaffInfo(staffId);
}
};
Thanks in advance!!
In the code you posted, there are two main problems. One in the template, and one in the controller logic.
Your template is the following :
<div data-ng-controller="staffController as staffCtrl" id="providerList" class="scrollDiv">
<!-- ngRepeat where you select the persons -->
</div>
<div data-ng-controller="staffController as staffCtrl">
<!-- ngRepeat where you show persons info -->
</div>
Here, you declared twice the controller, therefore, you have two instances of it. When you select the persons, you are storing the info in the data structures of the first instance. But the part of the view that displays the infos is working with other instances of the data structures, that are undefined or empty. The controller should be declared on a parent element of the two divs.
The second mistake is the following :
if (!self.infoList.length > 0) {
self.infoList = [];
}
You probably meant :
if (!self.infoList) {
self.infoList = [];
}
which could be rewrited as :
self.infoList = self.infoList || [];

Pass value from filter as argument for js function in angular

When I attempt to pass a value from ng-repeat into a function it seems to read the data literally from the text put in , whereas I would like to pass the values in....
<li ng-repeat="event in filtered = (events | filter:query) | orderBy:'-event_date'" >
<div class="event-info">
<strong>{{event.event_name}}</strong><br />
</div>
<div ng-click="prepare_edit('{{event.event_name}}')" >EDIT</div>
</li>
Controller:
$scope.prepare_edit = function(event_name) {
window.alert(event_name);
}
OUTPUTS: {{event.event_name}} , whereas I would like the actual value
You don't need to use braces {{}} in ng-click. If you have put "event" var inside $scope you can just use it.
<div ng-click="prepare_edit(event.event_name)" >EDIT</div>
Here is a FIDDLE

Angularjs toggle between input and span values in a table row

I have following code in my html where I want to toggle input and span fields. In a table row.
<table>
<tbody ng-repeat="(i, cont) in char.items">
<tr>
<td>
<div>
<a ng-click="choose()">
<input type="text" ng-model="item.desc" ng-show="sho==1" />
<span ng-show="sho==0">{{item.type}}</span></a>
</div>
</td>
</tr>
</tbody>
</table>
<div ng-click="addRows(char)" style="WIDTH: 974px">Add Row</div>
In my controller I have
app.controller("testCtrl", function($scope) {
$scope.sho=0;
$scope.addRows = function(char) {
if (typeof char.items == 'undefined') {
char.items = [];
}
char.items.push({ des: '', type: '', price: '', charge__id: ''});
};
$scope.choose= function() {
//some values are retrieved than I want to toggle so it shows the
//want to set sho=1 so input is hidden instead the span vaue is shown
$scope.sho=1;
};
});
Problem is when I set $scope.sho=1; it shows span value in all the row of the table.
While I add a new row I just want to show the input box leaving the other rows already inserted with span values.
Pleae let me know how can i set ng-show for each row in table.
Thanks
Since ng-repeat creates a child scope for each item you can leverage that within a directive. The parent scope of the directive will be the child scope created by ng-repeat and therefore isolated from other repeaters
Move your choose and sho out of main controller and put them into directive scope.
<div editable>
<a ng-click="choose()"></a>
<input type="text" ng-model="item.desc" ng-show="!sho" />
<span ng-show="sho">{{item.type}}</span>
</div>
app.directive('editable', function () {
return function (scope, elem, attrs) {
scope.sho = true;
scope.choose = function () {
scope.sho = !scope.sho;
}
}
});
This is the simplest version possible without going to isolated scope within the directive and without considering factors like more than one of these editables in a row.
For more insulated feature rich version would consider using a more robust directive like x-editable
I have trouble understanding what your code is actually used for. But my guess would be for you to pass the current item into the choose function and set a flag on the item itself. If you modify your ng-show and ng-hide attributes to react to this flag on each item, I guess you would reach your goal.
<a ng-click="choose(item)">
<input type="text" ng-model="item.desc" ng-show="item.sho==1" />
<span ng-show="item.sho==0">{{item.type}}</span></a>
</div>
And in your choose function you would do something like this:
$scope.choose= function(item) {
item.sho=1;
};
This is only a wild guess though, since it isn't quite clear to me what you are trying to accomplish.
Two things that come to mind immediately are:
1 - Pass in the item with the function and have the function accept an argument.
<a ng-click="choose(sho)">
and then in your controller
$scope.choose= function(sho) {
sho = 1;
};
2 - Just make ng-click set the value to one..
<a ng-click="sho = 1">

Categories