I have a problem with incrementing value in ngFor. I have two methods for increment and decrement, the code is below. NgFor is really long so I will post only ts file and HTML which has increment and decrement actions. My ngFor goes like this *ngFor="let product of catalogProducts. The main problem is that when I want to increase the value of input in ngFor, but it updates all inputs in ngFor...
TS file
#Input() counterValue = 0;
increment(index) {
this.counterValue++;
}
decrement() {
if(this.counterValue <= 0) return false;
this.counterValue--;
}
HTML
<div class="incrementer">
<div class="column">
<button (click)="decrement()">
-
</button>
</div>
<div class="column">
<input class="count" [value]="counterValue" [(ngModel)]="product.quantity" />
</div>
<div class="column">
<button (click)="increment(index)">
+
</button>
</div>
</div>
Related
I'm working on an angular task and the goal is to add items to be purchase into localStorage before adding to cart.
I have four different object that users can add, item can be added several time, so the general condition is if object exist on localStorage and user add it other time I should update quantity attribute, if not add new object with new attribute quantity = 1.
here is the service :
addGiftToCard(type) {
let cardParse = JSON.parse(localStorage.getItem('cart')) || []
let index = _.findIndex(cardParse, item => item.type && item.id == type.id)
if (index == -1) {
type.qte = 1
cardParse.push(type)
} else {
cardParse[index].qte += 1
}
localStorage.setItem('cart', JSON.stringify(cardParse))
}
and the function in my component :
addGiftToCart(type) {
let index = this.cartService.addGiftToCard({ type: type })
}
html :
<div class="available-checks" *ngIf="!(types === null)">
<div class="row checks-list">
<div class="col-md-3" *ngFor="let type of types">
<div class="card">
<div class="card-body">
<div class="row">
<div class="col-8 logo-container justify-content-center">
<img class="card-logo" src="../../../assets/images/logo-gold.svg" alt="">
</div>
<div class="col-4 icon-container justify-content-center text-center">
<img [src]=" serverUrl+'/'+type?.image" alt="" style="width: 50px;height: 50px;">
</div>
</div>
<div class="row desc-title-c">
<p class="desc-title">Chèque cadeau</p>
</div>
<div class="row type-c">
<p class="check-type">{{type.type}}</p>
</div>
<div class="offer-container">
<p class="offer">{{type.designation}}</p>
<p class="price">{{type.amount}}€</p>
</div>
<div class="">
<button class="btn btn-footer" (click)="addGiftToCart(type)">Ajouter au panier</button>
</div>
</div>
</div>
</div>
</div>
</div>
The problem I got here is the one object added what ever the item is and only the quantity attribute counter updated and got json format like that :
and If I set type = type.type in the service function It works and I got json like that
I need the json element type do not contain an attribute qty, qty should be outside the object attributes like the first image !
I think you should have used item.type.id instead of item.id like below
let index = _.findIndex(cardParse, item => item.type && item.type.id == type.id)
I have an array of objects and you can edit the name of each one but then I click to edit one all of the names of the items open, I am wondering how do to fix this.
<div *ngFor="let stop of fave; let i = index" attr.data="{{stop.Type}}">
<div class="card m-1">
<div class="card-body">
<div class="card-text">
<div class="row">
<label class="name" *ngIf="!toggleName" (click)="toggleName = true">{{stop.Name}}</label>
<div class="md-form" *ngIf="toggleName">
<input (keydown.enter)="updateStopName(i, stop.id); toggleName = false" placeholder="Chnage Stop Name" [(ngModel)]="stopName" required mdbInput type="text"
id="form1" class="form-control">
</div>
</div>
<div class="custom">
<img *ngIf="stop.Type === 'Train'" class="train-icon" style="width: 40px; height:40px"
src="assets/img/icon_trian.png" />
<img *ngIf="stop.Type === 'bus'" style="width: 40px; height:40px" src="assets/img/icon_bus.png" />
<img *ngIf="stop.Type === 'Luas'" style="width: 40px; height:40px"
src="assets/img/icon_tram.png" />
</div>
<label class="col-4 custom-label">Stop</label>
<label class="col-5 custom-service-label">Service</label>
<div class="row">
<span class="col-5 stop"> {{stop.StopNo}}</span>
<span style="padding-left:31%;" class="col-6 stop"> {{stop.Type | titlecase}}</span>
</div>
<hr />
<div class="row">
<div class="panel col-7" (click)="getRealtimeInfo({stop: stop.StopNo, type: stop.Type})">
<img class="panel-realtime" src="assets/img/icon_view.png" />
</div>
<div class="panel col-5" (click)="deleteFav(stop.id, i)">
<img class="panel-remove" src="assets/img/icon_remove.png" />
</div>
</div>
</div>
</div>
</div>
</div>
I know its something to do with the index but I am not sure how to write the code to only open the one I clicked on
As you can see at the moment all of them open any help is very much appreciated
If you want to open one at a time, you can use the index and of the item and a boolean. When clicked, set the index value to toggl if it's not already assigned, else assign it null (so that we can close the opened div on same click), and then show the content you want, when toggl === i. Something like:
<div *ngFor="let stop of fave; let i = index">
<label (click)="toggl === i ? toggl = null : toggl = i">Stuff!</label>
<div *ngIf="toggl === i">
<!-- ... -->
</div>
</div>
DEMO: StackBlitz
In your component declare one array
hideme=[];
In your html
<div *ngFor="let stop of fave; let i = index" attr.data="{{stop.Type}}">
<a (click)="hideme[i] = !hideme[i]">show/hide</a>
<div [hidden]="hideme[i]">The content will show/hide</div>
</div>
You have a unique id value inside your array, then you can do it like this:
<div *ngFor="let row of myDataList">
<div [attr.id]="row.myId">{{ row.myValue }}</div>
</div>
Assign an id to your input fields and they will work fine. Right now all of them have same id.
Use this code below as an example:
In your component, create a mapping like so:
itemStates: { [uniqueId: string]: boolean } = {};
Within your on click function:
itemClicked(uniqueId: string) {
let opened: boolean = this.itemStates[uniqueId];
if (opened !== undefined) {
opened = !opened; // Invert the result
} else {
opened = true;
}
}
In your HTML:
<div *ngFor="let item of items">
<h1 (click)="itemClicked(item.uniqueId)">{{ item.name }}</h1>
<div *ngIf="itemStates[item.uniqueId] == true">
<p>This item is open!</p>
</div>
</div>
Essentially, each item in your array should have a unique identifier. The itemStates object acts as a dictionary, with each unique ID having an associated true/false value indicating whether or not the item is open.
Edit: The accepted answer to this question is very simple and works great but this example may suit those who need to have the ability to have more than one item open at once.
I have a v-for loop to display a single result at a time based on the selected :key from another data value counter. While this works great, the problem lies with adding <transitions> when the value updates, causing both to appear briefly and jump about the page while the previous item transition disappears.
From what I can see, the issue is that all the tag results are still there on the DOM from the v-for and simply transitioning between the two.
Is there a better way to achieve this so only the {{tag}} values are updated based on the key?
<div v-for="tag in tags" :key="tag.id">
<transition name="fade">
<div v-if="tag.id == counter">
<div class="tag-col--prod-img mb-4">
<img class="img-fluid" :src="tag.thumb" />
</div>
<h5 class="mb-5">{{tag.heading}}</h5>
<div class="mb-3">
<h1>{{ tag.title }}</h1>
</div>
<h2 class="mb-3">{{ tag.price }}</h2>
<p class="mb-4">
{{tag.detail}}
</p>
<a :href="tag.link" target="_blank">
<button class="btn btn-primary">View product</button>
</a>
</div>
</transition>
</div>
Here's how to do it and take advantage of Vue's computed properties:
<transition name="fade">
<h5 class="mb-5">{{activeTag.heading}}</h5>
<!-- The rest -->
</transition>
Add this to your component:
computed: {
activeTag() {
return this.tags.find(({ id }) => id === this.counter);
}
}
activeTag will be reevaluated each time tags or counter changes, causing an update to the DOM elements referencing activeTag's properties.
im new to js and im trying to manipulate values of some h4 and some other tags then do some little math on each and return the new value back to the tag.
thanks to #drydenlong & #oliverong i was able to achieve this so far:
<div class="container">
<div class="row">
<div class="col-md-12">
<div class="input-group input-group-lg">
<span class="input-group-addon">Discount</span>
<input class="discount" type="text" placeholder="eg 30.22">
</div>
</div>
</div>
<div class="row">
<div class="col-md-4">
<div class="product">
<h2>Product 1</h2>
<h4 class="maththis">100</h4>
</div>
</div>
<div class="col-md-4">
<div class="product">
<h2>Product 2</h2>
<h4 class="maththis">200</h4>
</div>
</div>
<div class="col-md-4">
<div class="product">
<h2>Product 3</h2>
<h4 class="maththis" id="total">300</h4>
</div>
</div>
</div>
and this basic js
$('.discount').on('input',function(){
var base = parseInt($('.maththis').html());
var dis = $('.discount').val();
var newsum = dis - base;
$('.maththis').text(newsum);
});
i ran into some troubles and got stucked now :(
1) the calculation happens on keydown and that changes the values instantly, so there is also no going back. no new calculation possible unless relaoding page.
2) since im trying to change more values at once i also need to handle global variables i guess because in this example the value of text(). adds just the strings of the variable maththis toghether instead of each. so it makes 100 + 200 + 300= 100200300. this miscalculation gives also a NaN back.
how can i fix it so that the calculation happens on each class="maththis" seperatly and not together and how can i fix the value problem of class="maththis", so that i keep the original value somewhere incase i need to make changes on the discount value?
PEN: https://codepen.io/vup/pen/RxKjrw
PEN click function: https://codepen.io/vup/pen/Qadaqz
thanks alot <3
.text() will get you what is inside of the h1 element, so something like this will return 100:
var h1Val = $(".maththis").text();
Then you can use val() on whatever input field you have to do the math. In your case, it would be something like this:
var discount = $(".discount").val();
var h1Val = $(".maththis").text();
var total = h1Val - discount;
//Do whatever you need with the result
Here is a working example:
$('.discount').on('input',function(){
var base = $('.maththis').text();
var dis = $('.discount').val();
var total = base - dis;
$('#total').text('Total: '+total);
});
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.1/jquery.min.js"></script>
<input class="discount" type="text" />
<h1 class="maththis">100</h1>
<span id="total"></span>
In javascript:
let num = +document.getElementsByTagName('h1')[0].innerText
In jquery:
let num = +$('h1').text();
I have this https://codepen.io/anon/pen/RgQOWz
It has increment/decrement and it does what I want, but the problem is they both change together and I want to separate counter 1 from counter 2 meaning if I want to change counter 1 values it doesn't have to effect counter 2 and vice versa.
I can basically give counter 2 different class names and write another script for it but that is a lot of work considering the counters will be many.
so how do I do this?
Javascript code
$(".increment").click(function() {
var score1 = $(".score").val();
score1++;
$(".score").val(score1);
});
$(".decrement").click(function() {
var score1 = $(".score").val();
if (score1 == 0) {
} else {
score1--;
$(".score").val(score1);
}
});
Change you JavaScript to this
$(".increment").click(function() {
var score1 = $(this).next().find('.score').val();
score1++;
$($(this).next().find('.score').val(score1));
});
$(".decrement").click(function() {
var score1 = $(this).prev().find('.score').val();
if (score1 == 0) {
} else {
score1--;
$(this).prev().find('.score').val(score1);
}
});
instead of setting value to .score find the score based on which increment or decrement selection was selected
here is a working codepen https://codepen.io/anon/pen/xrYoRr#anon-login
Both counters using elements with the same .score class to keep the score. So each element updates both .score. You should either use IDs instead of classes, or (preferably) dynamically create elements and assign behaviors directly.
For example:
Add onClick event to document.createElement("th")
jQuery how to bind onclick event to dynamically added HTML element
I have made some changes to your code:
<div class="row">
<div class="col-xs-6">
<div class="row end-xs">
<div class="row middle-xs">
<p>Counter1</p>
</div>
<div class="prdin">
<div class="increment-c1">
<i class="icon-arrow-up icons"></i>
</div>
<div id="input1">
<input type="number" class="score1" value="0">
</div>
<div class="decrement-c1">
<i class="icon-arrow-down icons"></i>
</div>
</div>
</div>
</div>
<div class="col-xs-6">
<div class="row start-xs">
<div class="prdin">
<div class="increment-c2">
<i class="icon-arrow-up icons"></i>
</div>
<div id="input2">
<input type="number" class="score2" value="0">
</div>
<div class="decrement-c2">
<i class="icon-arrow-down icons"></i>
</div>
</div>
<div class="row middle-xs">
<p>Counter2</p>
</div>
</div>
</div>
</div>
And javascript code as below:
$(".increment-c1").click(function() {
var score1 = $(".score1").val();
score1++;
$(".score1").val(score1);
});
$(".decrement-c1").click(function() {
var score1 = $(".score1").val();
if (score1 == 0) {
} else {
score1--;
$(".score1").val(score1);
}
});
$(".increment-c2").click(function() {
var score1 = $(".score2").val();
score1++;
$(".score2").val(score1);
});
$(".decrement-c2").click(function() {
var score1 = $(".score2").val();
if (score1 == 0) {
} else {
score1--;
$(".score2").val(score1);
}
});