I read data from these child components of type custom note and i keep track of them using index, i.
<div *ngFor="let x of Pages; let i = index; trackBy: trackNotes">
<app-custom-note id="inputField{{ i }}" (pagesEvent)="testUpdate($event, i)"></app-custom-note>
<button size="small" color="danger" (click)="deletePage(i)">Delete Page</button>
</div>
<button size="small" color="success" (click)="addPage()">Add new Page</button>
when i click delete, i splice the elements at the parent component. but the information thats been deleted is still on the page
testUpdate(card: FormGroup | FormArray, index: number) {
let cards = new CustomPage(card.value);
this.CustomPageEvent[index] = (cards);
}
trackNotes(index) {
return index
}
deletePage(index) {
this.CustomPageEvent.splice(index,1) // holds data
this.Pages.splice(index,1) // holds length of array
}
addPage() {
this.Pages.push('A page');
if (this.eventComponents.find(e => e.component === 'CustomNoteComponent')) {
let orderOfPage = this.eventComponents.filter(ord => (ord.component == "CustomNoteComponent")).pop();
this.eventComponents.push({ id: definedId, component: "CustomNoteComponent", order: addedOrder })
}
}
when deletePage(0) is executed, the component at index 1 is deleted. how do I delete the <custom-component> representational to the index i thats been selected?
i tried deleting by using the trackby attribute but couldnt link it to an actual delete function that deletes the component
Related
In my application I have saved the data when we click on it(we can add the multiple data by entering some data and save the multiple data by clicking the save button).
.component.html
<ng-container *ngFor="let categoryDetail of selectedCategoryDetails">
<div class="__header">
<div>
<b>{{ categoryDetail.category }}</b>
</div>
</div>
<div
class="clinical-note__category__details"
*ngIf="categoryDetail.showDetails">
<ul>
<li class="habit-list"
*ngFor="let habits of categoryDetail.habitDetails" >
<div class="target-details">
<b>{{ clinicalNoteLabels.target }}: </b
><span class="habit-list__value">{{ habits.target }}</span>
</div>
</li>
</ul>
<div class="habit-footer">
<span class="m-l-10"
[popoverOnHover]="false"
type="button"
[popover]="customHabitPopovers"><i class="fa fa-trash-o" ></i> Delete</span>
</div>
<div class="clinical-note__popoverdelete">
<popover-content #customHabitPopovers [closeOnClickOutside]="true">
<h5>Do you want to delete this habit?</h5>
<button
class="btn-primary clinical-note__save" (click)="deletedata(habits);customHabitPopovers.hide()">yes </button>
</popover-content></div>
</div>
</ng-container>
In the above code when we click on delete button it will show some popup having buttons yes(implemented in above code) and now so my requirement is when we clcik on yes button in from the popover it has to delete the particular one.
.component.ts
public saveHealthyHabits() {
let isCategoryExist = false;
let categoryDetails = {
category: this.clinicalNoteForm.controls.category.value,
habitDetails: this.healthyHabits.value,
showDetails: true,
};
if (this.customHabitList.length) {
categoryDetails.habitDetails = categoryDetails.habitDetails.concat(
this.customHabitList
);
this.customHabitList = [];
}
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category === categoryDetails.category) {
isCategoryExist = true;
selectedCategory.habitDetails = selectedCategory.habitDetails.concat(
categoryDetails.habitDetails
);
}
});
}
if (!this.selectedCategoryDetails || !isCategoryExist) {
this.selectedCategoryDetails.push(categoryDetails);
}
this.clinicalNoteForm.patchValue({
category: null,
});
this.healthyHabits.clear();
public deletedata(habits){
if (this.selectedCategoryDetails) {
this.selectedCategoryDetails.forEach((selectedCategory) => {
if (selectedCategory.category ==categoryDetails.category) {
isCategoryExist = true;
this.selectedCategoryDetails.splice(habits, 1);
}
});
}
}
The above code I have written is for saving the data(we can enter multiple data and save multiple )
Like the above I have to delete the particular one when we click on yes button from the popover.
Can anyone help me on the same
If you're iterating in your html like:
<... *ngFor="let categoryDetails of selectedCategoryDetails" ...>
and your button with deletedata() is in the scope of ngFor, you can:
Change your iteration to include index of an item and trackBy function for updating the array in view:
<... *ngFor="let categoryDetails of selectedCategoryDetails; let i = index; trackBy: trackByFn" ...>
On the button click pass the index to deletedata() method like:
deletedata(index)
Create your deletedata method like:
deletedata(index:number){
this.selectedCategoryDetails.splice(index, 1);
// other code here, like calling api
// to update the selectedCategoryDetails source
// etc.
}
Create trackByFn method like:
trackByFn(index,item){
return index;
}
EDIT: Without index
If you want to iterate over selectedCategoryDetails in the ts file, without using ngFor with index in your html, you can have your deletedata like this:
deletedata(categoryDetails:any){
for (let i = this.selectedCategoryDetails.length - 1; i >= 0; i--) {
if (this.selectedCategoryDetails[i] === categoryDetails.category) {
this.selectedCategoryDetails.splice(i, 1);
}
}
}
It will iterate over selectedCategoryDetails backwards and remove the categoryDetails if it finds it in the array of objects.
Now, you only need to pass the categoryDetails to deletedata in your html:
(click)="deletedata(categoryDetails);customHabitPopovers.hide()"
The below creates a menu list, of three options which can be clicked like a navigation:
<ul>
<li
*ngFor="let filter of filterList"
(click)="filterChanged($event)"
[attr.data-nav]="filter"
>{{ filter }}</li>
</ul>
Show/hide the below component based on the values of the top menu events, let's say if I have 3 options in the menu I clicked the second and third value, it should hide the below component
<app-test-component>
</app-test-component>
Should I use
*ngIf="filter !== 'valueA' && 'valueB' "
I have to admit, this is not the best solution but from what I could understand from your question this is what I came up with.
You can refer to the link below for an working example (click on the 'li' tags)
https://stackblitz.com/edit/angular-ivy-u8oxlv?file=src/app/app.component.ts
app.component.ts
export class AppComponent {
filterList = ['First', 'Second', 'Third'];
// List of items that has been selected by clicking
selectedList = [];
// List of items that needs to be selected for the condition
itemsNeededForDisplay = ['Second', 'Third'];
// Flag to either hide or display the component
displayComponent: boolean = true;
filterChanged(text) {
// If we already selected the item, we should remove it from our list, otherwise add it to the list
const foundIndex = this.selectedList.findIndex((sl) => sl == text);
if (foundIndex == -1) this.selectedList.push(text);
else this.selectedList.splice(foundIndex, 1);
// After every click we should check if we should hide or display the component
this.handleComponentDisplayRule();
}
handleComponentDisplayRule() {
// This filter will return us a new array that matches the filter (we look if the selectedList has the 'itemsNeededForDisplay')
let foundSelectedFilters = this.selectedList.filter((elem) => {
return this.itemsNeededForDisplay.indexOf(elem) > -1;
});
// If we get the length same as our 'itemNeededForDisplay' it means that our list has all the values we are looking for
this.displayComponent = foundSelectedFilters.length != this.itemsNeededForDisplay.length;
}
}
app.component.html
<span> Selected: {{ selectedList | json }} </span>
<ul>
<li
*ngFor="let filter of filterList"
(click)="filterChanged(filter)"
[attr.data-nav]="filter"
>
{{ filter }}
</li>
</ul>
<hello *ngIf="displayComponent"></hello>
hello.component.ts
import { Component, Input } from '#angular/core';
#Component({
selector: 'hello',
template: `<h1>Hello {{name}}!</h1>`,
styles: [`h1 { font-family: Lato; }`]
})
export class HelloComponent {
#Input() name: string;
}
Add ngIf to your component selector and don't validate it on what value it's not but on the value it should be:
<app-test-component *ngIf="filterValue == 'valueC'"></app-test-component>
Additional advice:
I suggest you keep 'valueC' in a constant within the component instead of the HTML, considering you'll reference to that value in the HTML and in your filterChanged() function. It'll prevent future mistakes should you want to change the value.
Then in the HTML you have
<app-test-component *ngIf="filterValue == valueC"></app-test-component>
And in the component
const valueA = 'ValueA';
const valueB = 'ValueB';
const valueC = 'ValueC';
If your filterChanged() function only sets the filterValue you can also just set the value straight from the html:
<ul>
<li *ngFor="let filter of filterList"
(click)="filterValue = filter"
[attr.data-nav]="filter">
{{ filter }}
</li>
</ul>
I have a v-for loop that populates <audio> components backed by an array of blobs and the buttons to play and delete it.
<div v-for="(file, index) in soundFiles" :key="index">
<audio :src="file.url" :id="file.uuid" :ref="el => audios[file.uuid] = el" />{{ file.name }}
<button :id="file.uuid" v-on:click="playSound">></button>
<button :id="file.uuid" v-on:click="deleteSound">X</button>
</div>
The way :ref is used is taken from this example
const soundFiles = ref([]);
const audios = ref([]);
//......
function playSound(e) {
const soundId = e.target.id;
audios.value[soundId].play();
}
When I add a sound and then click the play button I get an empty soundId. The inspector also shows that the audio and buttons don't have any attributes. When I reload the page the data is pulled from the local storage, the underlying array for v-for is populated in the setup() function and the elements are created with attributes just fine.
What should I do to set the ids of my elements dynamically?
<div v-for="(file, index) in soundFiles" :key="index">
<audio :src="file.url" :id="file.uuid" :ref="el => audios[file.uuid] = el" />{{ file.name }}
<button :id="file.uuid" v-on:click="playSound(file.uuid)">></button>
<button :id="file.uuid" v-on:click="deleteSound">X</button>
</div>
function playSound(id) {
audios.value[id].play();
}
Why don't you write it like that?
Don't use index as v-for key if you add anything dynamically, make the key ID. If your data don't have id as default, add it on the fly in computed property:
<div v-for="file in files" :key="file.id"></div>
data() {
return {
soundFiles: [ array of your files ],
}
},
computed: {
files() {
return soundFiles.map(x => {
x.id = Math.floor(Math.random() * 1000000000)
return x;
})
}
}
I am creating a shopping cart type button to count the number of times clicked, the button should call a function when clicked with the parameter of the id, however, it won't call the function with the correct parameter
I have tried adding {{id}} and :onClick="addThisToCart({{id}} but getting a ton of errors.
Heres my code
Vue.component('movietable', {
props: ['title', 'price', 'mid'],
template: `
<tr>
<td>{{title}}</td>
<td>{{price}}</td>
<td>
<div class="quantity">
<button onClick="addThisToCart({{mid}}">You clicked me {{ count }} times.</button>
</div>
</td>
</tr>
`,
data: function () {
return {
count: 0
}
},
});
mid is being defined in the properties section of the vue element
and then the function
var cart = 0;
function addThisToCart(movieId) {
var movieId = this.mid;
this.cart += 1;
this.cart += 1;
console.log(movieId);
console.log(cart);
}
It should add +1 to cart every time the button is clicked, however, getting a ton of errors and instead of sending '4434' it is sending {{mid}}
You can use
<button #click="addThisToCart(mid)">You clicked me {{ count }} times.</button>
No curly braces for the argument of the function.
I am trying to build a custom element to manage simple lists, renaming the items and changing their order. Unfortunately I'm noticing some weird behavior that's actually really hard to pin down.
Typing into the inputs does not appear to be recognized as changes for Aurelia to update the item
When typing/changing one item after page load and then changing its position in array via those methods, the index of the item seems lost (turns into -1). If the item isn't changed via input field, the index in the array is recognized correctly and sorting works.
Are there any known issues with arrays, binding and maybe even child elements? What are the battle tested approached to get the desired behavior? Thanks a lot!
Parent Element
...
<list items.bind="list"></list>
...
List Element
<template>
<div class="input-group" repeat.for="item of items">
<input value.bind="item" class="input" type="text" placeholder="Item" autofocus>
<a click.delegate="deleteItem(item)">X</a>
<a click.delegate="moveItemUp(item)">^</a>
<a click.delegate="moveItemDown(item)">v</a>
</div>
<a click.delegate="addItem()">Add Item</a>
List JS
export class List {
#bindable({defaultBindingMode: bindingMode.twoWay}) items;
constructor() {}
addItem() {
this.items.push('new')
}
deleteItem(item) {
let i = this.items.indexOf(item)
this.items.splice(i, 1)
}
moveItemUp(item) {
let i = this.items.indexOf(item)
if (i === 0) return
let temp = item
this.items.splice(i, 1)
this.items.splice(i - 1, 0, temp)
}
moveItemDown(item) {
let i = this.items.indexOf(item)
if (i === this.items.length) return
let temp = item
this.items.splice(i, 1)
this.items.splice(i, 0, temp)
}
}
repeat.for has several contextual variables you can leverage of. [Documentation]
Gist demo: https://gist.run/?id=1c8f78d8a774cc859c9ee2b1ee2c97f3
Current item's correct position can be determined by using $index contextual variable instead of items.indexOf(item).
Databound values of inputs will be preserved by passing item to items.slice(newIndex, item).
If you need to observe array changes, CollectionObserver could be a great fit for that. More details here: Observing Objects and Arrays in Aurelia.
list.js
import { bindable, bindingMode } from 'aurelia-framework';
export class List {
#bindable({defaultBindingMode: bindingMode.twoWay}) items;
constructor() {}
addItem() {
this.items.push(`New Item ${this.items.length + 1}`);
}
deleteItem(i) {
this.items.splice(i, 1);
}
moveItemUp(i, item) {
if (i === 0)
return;
this.moveItem(i, i - 1, item);
}
moveItemDown(i, item) {
if (i === this.items.length - 1)
return;
this.moveItem(i, i + 1, item);
}
moveItem(oldIndex, newIndex, item) {
this.items.splice(oldIndex, 1);
this.items.splice(newIndex, 0, item);
}
}
list.html
<template>
<div class="input-group" repeat.for="item of items">
<input value.bind="item" class="input" type="text" placeholder="Item" autofocus> |
<a click.delegate="deleteItem($index)"><i class="fa fa-close"></i></a> |
<a click.delegate="moveItemUp($index, item)"><i class="fa fa-arrow-up"></i></a> |
<a click.delegate="moveItemDown($index, item)"><i class="fa fa-arrow-down"></i></a>
</div>
<a click.delegate="addItem()">Add Item</a>
</template>
I believe this has to do with the immutability of strings. That is, strings can't be modified, so when you modify a value in the textbox, the array element is actually replaced instead of modified. That's why you're losing the binding.
Here's a gist that demonstrates it working correctly when binding to a list of objects.
https://gist.run/?id=22d186d866ac08bd4a198131cc5b4913