So I have a list of phones that I add new input lines to, and I have a remove method set, for each individual row.
This is the template:
<div formArrayName="mobiles">
<div *ngFor="let mobile of mobiles.controls; let i = index" [formGroupName]="i">
<div class="d-flex align-items-center" [class.pt-4]="i > 0">
<input class="input" type="text" formControlName="phone">
<a class="input-remove ml-3">
<i class="icon icon-trash h3 text-primary" (click)="removeMobile()"></i>
</a>
</div>
</div>
</div>
And these are my methods that are within the respective component:
get mobiles(): FormArray {
return this.form.get("mobiles") as FormArray;
}
get stations(): FormArray {
return this.form.get("stations") as FormArray;
}
addMobile() {
this.mobiles.push(this.fb.group(new PhoneFormGroup()));
}
addStation() {
this.stations.push(this.fb.group(new PhoneFormGroup()));
}
removeMobile(index: number) {
this.mobiles.removeAt(index);
if (this.mobiles.controls.length == 0) {
this.addMobile();
}
}
removeStation(index: number) {
this.stations.removeAt(index);
if (this.mobiles.controls.length == 0) {
this.addStation();
}
}
The problem here is, that whenever I click on the delete button, it deletes the first item(index[0]) from the list, and I want it to delete the specific item I have selected for deletion.
What am I doing wrong?
You don't pass index param to removeMobile function from your HTML template.
<i class="icon icon-trash h3 text-primary" (click)="removeMobile(i)"></i>
Related
I am trying to show the message Cart empty in my modal when the cart is empty. When a product is added to the cart, that message should be removed and replaced with the product.
The product being added to the modal is working, but when I delete, and the quantity is 0, the message does not show.
I am using Vuex, but when tryin to use v-if and v-else within the v-for loop, the Cart empty message does not show. Below is an example of my MinCart.vue modal.
<div class="modal-body" v-for="item in this.$store.state.cart">
<div v-if="noItemsInCart">Cart empty</div>
<div v-else>
<div class="card mb-3 border-0">
<div class="row no-gutters">
<div class="col-sm-4">
<img :src="item.productImage" width="120px" class="align-self-center mr-3" alt="">
</div>
<div class="col-sm-6">
<div class="card-block px-2">
<h6 class="card-title">{{ item.productName }}</h6>
<p class="card-text">{{ item.productPrice | currency }}</p>
<p class="card-text">Quantity: {{ item.productQuantity }}</p>
</div>
</div>
<div class="col-sm-2">
<div class="card-body pt-1">
<div class="d-flex justify-content-between align-items-center">
<a #click="$store.commit('removeFromCart', item)" type="button" class="card-link-secondary small text-uppercase mr-3">
<i class="fas fa-trash-alt mr-1"></i>
</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
Here is my computed prop:
computed: {
noItemsInCart() {
return this.$store.getters.cartEmpty
}
}
And finally, my store.js with the getter method;
state: {
cart: cart ? JSON.parse(cart) : [],
},
getters: {
cartEmpty: state => {
let qty = 0;
state.cart.filter((item) => {
qty = item.productQuantity
})
}
}
Your Vuex getter doesn't return anything, so it's always falsy, which makes the computed prop (noItemsInCart) always truthy.
But your getter also isn't using Array.prototype.filter correctly. The filter is assigning qty instead of comparing it, and the filter callback returns nothing.
I'm guessing you were trying to count the items in the cart by checking productQuantity of each item. That would be done with Array.prototype.some like this:
{
getters: {
// find any item that has a positive `productQuantity` value
cartEmpty: state => state.cart.some(item => item.productQuantity > 0)
}
}
Also, your template should move the v-for loop into the v-else block. Otherwise, an empty cart will prevent the v-if block from rendering (thus no Cart empty message):
<div v-if="noItemsInCart">Cart empty</div>
<div v-else class="modal-body" v-for="item in this.$store.state.cart">
</div>
You don't need getter in this case. You just need to return in computed something like this:
computed: {
isEmptyCart() {
return Boolean(~this.$store.state.cart.length); // or use compare ...length > 0
}.
}
Or more beautiful example with mapState:
computed: {
...mapState({
cart: ({ cart }) => cart; // don't use this.$store.state.cart in template
isEmptyCart: ({ cart }) => Boolean(~cart.length),
}),
}
I figured out how to change the icon when more elements are added, but I can't figure out to how return it to the original icon while the cart is empty.
Javascript
let cartItems = document.getElementsByClassName('cart-container')[0];
if (cartItems.childElementCount <= 1) {
let cartBtn = document.getElementsByClassName('cart-btn')[0]
cartBtn.innerHTML = `
<i class="fa fa-cart-plus cart-btn text-danger"></i>`
}
HTML
<h1 class="cart-btn">
<i class="fa fa-shopping-cart"></i>
</h1>
<div class="container cart-container d-flex flex-column pb-5">
<div class="row mt-5 mb-4">
<div class="col">
</div>
</div>
</div>
I have a shopping cart button on a navbar that I need to switch between different states depending on if the cart is empty or not. I figured out how to change it to one state when an item was added to the cart, but couldn't figure out how to change it back to the original state when I emptied the cart. However, I used this code to accomplish that task.
Javascript
function checkNavBtn() {
let cartItems = document.getElementsByClassName('cart-container')[0];
let cartBtn = document.getElementsByClassName('fa-shopping-cart')[0];
if (cartItems.childElementCount >= 0) {
cartBtn.classList.add('fa-cart-plus', 'text-danger');
} if (cartItems.childElementCount <= 0) {
cartBtn.classList.remove('fa-cart-plus', 'text-danger');
}
}
So I'm currently doing a Calorie Counter project that consists on giving the user the option to firstly, add items with the respective name and number of calories, remove items or update them when clicking on an edit icon next to the item, and finally removing all items at once.
The UI will basically display all the items that the user has added (including the name and the number of calories), where each item will have an edit icon next to it, and if the icon is clicked, it will give the user the option to edit them and delete them.
I still haven't gotten to the edit part because I'm currently stuck in the delete part.
Let's say I have 3 items in the list, when I click on the edit button and then delete, everything works out fine, the html element is deleted and it looks good. If I repeat the process one more time it still works, but when I repeat the process one last time, the problem happens.
For some reason, when I hit the edit button nothing happens, I've checked and apparently the item array is completely empty, even though I only deleted 2 out of the 3 items.
I've tried everything and I've been completely stuck for 3 days straight.
// Item Controller
const ItemController = function() {
// Hard coded items
data = [{
name: "Hamburguer",
id: 0,
calories: 1000
},
{
name: "Pasta",
id: 1,
calories: 700
},
{
name: "Apple",
id: 2,
calories: 70
}
]
return {
getItems: function() {
return data;
},
deleteAllItems: function() {
data.items = [];
UIController().clearItems();
},
getTotalCalories: function() {
totalCalories = 0;
this.getItems().forEach(item => {
totalCalories += parseInt(item.calories)
});
UIController().changeToTotalCalories(totalCalories);
},
removeSingleItem: function(item, li) {
// Getting the index of the item
indexItem = items.getItems().indexOf(item);
// Deleting item from array
items.getItems().splice(indexItem, 1);
// Deleting li item from UI
li.remove();
console.log(items.getItems());
}
}
};
const items = ItemController();
// UI controller
const UIController = function() {
return {
displayItems: function(itemsPresented) {
itemsPresented.forEach(function(item) {
itemList = document.getElementById("item-list");
itemList.innerHTML += `
<li class="collection-item" id="${item.id}">
<strong>${item.name}: </strong><em>${item.calories} calories</em>
<a href="#" class="secondary-content">
<i class="edit-item fa fa-pencil">
</i>
</a>
</li>
`;
})
},
clearItems: function() {
itemList = document.getElementById("item-list");
itemList.innerHTML = "";
items.getTotalCalories();
},
changeToTotalCalories: function(totalCalories) {
document.querySelector(".total-calories").textContent = totalCalories;
},
}
}
const uiCtrl = UIController();
// So when the page loads, the hard coded items can be represented
uiCtrl.displayItems(items.getItems());
// To delete all the items at once
clearAllBtn = document.querySelector(".clear-btn");
clearAllBtn.addEventListener("click", (e) => {
items.deleteItems();
e.preventDefault();
})
// Getting the li element (The one that has all the hard-coded items)
itemList = document.getElementById("item-list");
itemList.addEventListener("click", e => {
// Checking if the user is clicking the Edit Icon
if (e.target.classList.contains("edit-item")) {
items.getItems().forEach(item => {
li = e.target.parentElement.parentElement;
// Getting the item that has the edit icon that the user clicked
if (item.id === parseInt(e.target.parentElement.parentElement.id)) {
// Putting the name and the calories of the item that is being edited in the input fields
document.getElementById("item-name").value = item.name;
document.getElementById("item-calories").value = item.calories;
// Changing the buttons so when the user edits an item, they have the options Update and Delete
document.querySelector(".add-btn").style.display = "none";
document.querySelector(".update-btn").style.display = "block";
document.querySelector(".delete-btn").style.display = "block";
document.querySelector(".back-btn").style.display = "none";
// If the user clicks the delete button
document.querySelector(".delete-btn").addEventListener("click", e => {
// Changing all the buttons back to normal
document.querySelector(".add-btn").style.display = "block";
document.querySelector(".update-btn").style.display = "none";
document.querySelector(".delete-btn").style.display = "none";
document.querySelector(".back-btn").style.display = "block";
// Clearing out the input fields
document.getElementById("item-name").value = "";
document.getElementById("item-calories").value = "";
// Deleting item
items.removeSingleItem(item, li);
// Updating the calories
items.getTotalCalories();
e.preventDefault();
});
}
});
}
})
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link href="https://stackpath.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet" integrity="sha384-wvfXpqpZZVQGK6TAh5PVlGOfQNHSoD2xbE+QkPxCAFlNEevoEH3Sl0sibVcOQVnN" crossorigin="anonymous">
<script src="https://code.jquery.com/jquery-3.5.1.min.js" integrity="sha256-9/aliU8dGd2tb6OSsuzixeV4y/faTqgFtohetphbbj0=" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<nav>
<div class="nav-wrapper blue">
<div class="container">
<a href="#" class="brand-logo center">
Tracalorie
</a>
<ul class="right">
<li>
<a href="#" class="clear-btn btn blue lighten-3">
Clear All
</a>
</li>
</ul>
</div>
</div>
</nav>
<br>
<div class="container">
<!-- Form Card -->
<div class="card">
<div class="card-content">
<span class="card-title">
Add Meal / Food Item
</span>
<form class="col">
<div class="row">
<div class="input-field col s6">
<input type="text" id="item-name" placeholder="Add item">
<label for="item-name">Meal</label>
</div>
<div class="input-field col s6">
<input type="text" id="item-calories" placeholder="Add calories">
<label for="item-calories">Calories</label>
</div>
<button class="add-btn btn blue darken-3"><i class="fa fa-plus"></i>
Add Meal</button>
<button style="display: none;" class="update-btn btn orange" display=><i class="fa fa-pencil-square-o"></i>
Update Meal</button>
<button style="display: none;" class="delete-btn btn red"><i class="fa fa-remove"></i>
Delete Meal</button>
<button class="back-btn btn grey pull-right"><i class="fa fa-chevron-circle-left"></i>
Back</button>
</div>
</form>
</div>
</div>
<!-- Calorie Count -->
<h3 class="center-align">Total Calories: <span class="total-calories">
0
</span></h3>
<!-- Item list -->
<ul id="item-list" class="collection">
</ul>
</div>
It seems like you add an eventListener to the delete button every single time a user clicks on the edit pencil. You never remove these eventListeners. So when the first edit is done, there is one delete event and one items gets deleted. The next time a user clicks on the edit button, a second event gets added to the same html element, thus two items gets deleted (both events will trigger one after the other). This becomes apparent when your hardcoded list would contain 10 items, you would see 1,2,3 and lastly 4 items disappear. I suggest you look into resetting/removing eventlisteners.
I am facing a problem in deleting item from an array. Array splice supposed to work but its not working like I want. Its always delete the item from last. I am using Vue.js . I am pushing item dynamically to an array. But after click remove its delete from the last. why I am facing this. I am attaching the codes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
</div>
</div>
</div>
</template>
<script>
import flowButton from '../assets/flow-button'
export default {
name: "textArea",
props:{
index : Number
},
data() {
return {
buttons : [],
btnClass : 'badge-primary',
}
}
components : {
flowButton
},
methods : {
addBtn () {
if(this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if(this.buttons.length < 3) {
this.buttons.push({
title : ''
});
}
},
remove(index) {
this.buttons.splice(index, 1)
}
}
}
</script>
This must be because of your flow-button I have tried to replicate your error but endup to this code. I just replaced the flow-button with input and it works. Try the code below.
Use v-bind:key="index", When Vue is updating a list of elements rendered with v-for, by default it uses an “in-place patch” strategy. If the order of the data items has changed, instead of moving the DOM elements to match the order of the items, Vue will patch each element in-place and make sure it reflects what should be rendered at that particular index. This is similar to the behavior of track-by="$index"
You have missing comma between data and components, I remove the component here it won't cause any error now, and more tips don't mixed double quotes with single qoutes.
<template>
<div>
<span class="badge badge-pill mb-10 px-10 py-5 btn-add" :class="btnClass" #click="addBtn"><i class="fa fa-plus mr-5"></i>Button</span>
<div class="block-content block-content-full block-content-sm bg-body-light font-size-sm" v-if="buttons.length > 0">
<div v-for="(item, index) in buttons" v-bind:key="index">
<div class="field-button">
<div class="delete_btn"><i #click="remove(index)" class="fa fa-trash-o">sdfsdff</i></div>
<input type="text" v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"/>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'textArea',
props: {
index: Number
},
data () {
return {
buttons: [],
btnClass: 'badge-primary'
}
},
methods: {
addBtn () {
if (this.buttons.length >= 2) {
this.btnClass = 'btn-secondary'
}
if (this.buttons.length < 3) {
this.buttons.push({
title: ''
})
}
},
remove (index) {
this.buttons.splice(index, 1)
}
}
}
</script>
I think that you may be facing a conflict with the index prop of your component. Try to use a different name for the index of your v-for loop:
<div v-for="(item, ind) in buttons">
<div class="field-button">
<div class="delete_btn"><i #click="remove(ind)" class="fa fa-trash-o"></i></div>
<flow-button v-model="item.title" :showLabel="false" className="btn btn-block min-width-125 mb-10 btn-border" mainWrapperClass="mb-0" wrapperClass="pt-0" placeholder="Button Title"></flow-button>
</div>
</div>
Try this. Removing an item correctly using this.
<div v-for="(item, ind) in buttons" :key="JSON.stringify(item)">
When I type in text to search something, displaying one character in text is very slow.
What is the problem ?
I have display 50 products with ngFor as below , if I display more than 50 products 100 or 150 typing in text is more slow.
what should I do to fix this problem ?
<div class="width_products products-animation " *ngFor="let product of productsService.products ; trackBy: $index" [ngClass]="{ 'width_products_open_menu':productsService.status_menu }" >
<span class="each_width_product" >
<div class="title_products more_detail_product" (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })">
{{product.product_title }}
<span class="glyphicon glyphicon-chevron-down"></span><br>
<div class=' glyphicon glyphicon-time'></div> {{product.product_date}}
</div>
<div class="image_product_primary " (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })">
<img class="image_product" src="../../assets/images/products_image/{{product.product_image}}">
</div>
<button (click)="product.product_in_wishList='true'; productsService.add_wish_list( product )" mat-button class="wish_list notCloseDropdawnFavorite notCloseDropdawnCard">
<span class="write_add_wish">{{dataservices.language.add_wishlist}}</span>
<mat-icon *ngIf="product.product_in_wishList == 'false' " class="notCloseDropdawnFavorite notCloseDropdawnCard">favorite_border</mat-icon>
<mat-icon *ngIf="product.product_in_wishList == 'true' " class="hearts_div_hover notCloseDropdawnFavorite notCloseDropdawnCard">favorite</mat-icon>
</button>
<div class="footer_products">
<span matTooltip="Views!">
<div class="button_footer_products">
<span class="glyphicon glyphicon-eye-open icon_eye"></span>
<div class="both_write ">
12889
</div>
</div>
</span>
<span matTooltip="Add to your card" class="notCloseDropdawnCard notCloseDropdawnFavorite " (click)="product.product_in_cartList='true'; productsService.add_cart_list( product )">
<div class="button_footer_products">
<span *ngIf="product.product_in_cartList=='false'" class="glyphicon glyphicon-plus icon_eye notCloseDropdawnCard notCloseDropdawnFavorite" ></span>
<span *ngIf="product.product_in_cartList=='true'" class="glyphicon glyphicon-ok icon_eye notCloseDropdawnCard notCloseDropdawnFavorite" ></span>
<div class="both_write ">
Cart
</div>
</div>
</span>
<span matTooltip="See Details!">
<div (click)="set_router({ path:product['company'].company_name+'/'+product.product_title , data:product.product_id , relative:true })" class="button_footer_products" >
<span class=" glyphicon glyphicon-option-horizontal icon_eye"></span>
<div class="both_write ">
More
</div>
</div>
</span>
</div>
<div class="prise_products">
Price:<del>$2500</del> $3500
</div>
<div class="plus_height"></div>
</span>
</div>
In header component I have a input type text as below :
<input type="text" class="kerkim" name="search" [(ngModel)]="typing_search" placeholder="
{{dataservices.language.searchproducts}}">
Debouce effect, e.g. do not run search immediately.
class Coponent {
private _timeoutId: number;
//to be called on search text changed
search(){
clearTimeout(this._timeoutId);
this._timeoutId = setTimeout(() => {
//do search stuff
}, 500) //play with delay
}
}
Cache prev results using search keyword.
When kyeword changes like so ["k","ke","key"] you do not need to refilter whole array.
class Search {
private _keywordChanges:string[] = [];
private _prevFilterResults: any[] = [];
private _allData: any[] = [];
search(keyword:string){
let prevKeyword = this.getPrevKeyword(),
toBeFiltered: any[];
if(keyword.match(keyword)){ //if it was "ke" and now it is "key"
//filter prev results only
toBeFiltered = this._prevFilterResults;
} else {
//filter prev results or even make cache for keyword
toBeFiltered = this._allData;
}
let results = toBeFiltered.filter(() => {});
this._prevFilterResults = results;
}
private getPrevKeyword(){
return this._keywordChanges[this._keywordChanges.length - 1];
}
Use for with break instead of Array.filter(), in some cases it may be helpfull. For example you have sorted array ["a","apple","b","banana"] and keyword "a".
function search(array:any[], keyword:string) {
//so
let results = [];
for(let i = 0; i < array.length; i++){
let item = array[i];
if(item.toString().startsWith(keyword)){
results.push(item);
} else {
break; //as b and banana left
}
}
return results;
}
Take a look at binary search. How to implement binary search in JavaScript
and hash table Hash table runtime complexity (insert, search and delete)
From my issue: every input field is slow due to many data. so i add "changeDetection: ChangeDetectionStrategy.OnPush" at where data reloaded, then everything work normal.
#Component({
selector: 'app-app-item',
templateUrl: './app-item.component.html',
styleUrls: ['./app-item.component.css'],
changeDetection: ChangeDetectionStrategy.OnPush,
})