Show/Hide angular component based on value from top menu item clicked - javascript

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>

Related

Angular How to delete a component from ngFor Loop

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

How to remove the selected data from saved data when we click on button in a selected one

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()"

how do a do a scroll in to view for a newly added list item in vueJS

I am using vueJS to build a task view applicatioon
when I add a new task I want the div that contains the list to immediately focus on the newly added list item
below is my html in the template for the list of task
<ul>
<li
v-for="task in filteredTasks"
:key="task.id"
id="taskListItem"
ref="taskListItem"
class="taskList d-flex align-items-center justify-content-between"
>
<span> {{ task.name }} </span>
</li>
</ul>
below is my functions in computed and methods to add and filter out tasks
script
computed : {
filteredTasks() {
return this.selectedUsers.length
? this.filteredOnProgress.filter((task) =>
task.userIds.some((id) => this.selectedUsers.includes(id))
)
: this.filteredOnProgress;
},
}
methods : {
addTaskName(){
if (this.newTaskName.name != "") {
this.addTask(this.newTaskName)
this.newTaskName.name = ""
}
}
},
I solve my problem like this
I gave my unordered list an id="taskListul"
and in methods I added a function:
scrollToEndOfTask() {
const taskScroll = this.$el.querySelector("#taskListul")
const scrollHeight = taskScroll.scrollHeight
taskScroll.scrollTop = scrollHeight
},
I called this function in update method of the vue instance
updated() {
this.scrollToEndOfTask()
},

Angular on click event for multiple items

What I am trying to do:
I am trying to have collapsible accordion style items on a page which will expand and collapse on a click event. They will expand when a certain class is added collapsible-panel--expanded.
How I am trying to achieve it:
On each of the items I have set a click event like so:
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass()" [class.collapsible-panel--expanded]="expanded" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
and in the function toggleClass() I have the following:
expanded = false;
toggleClass() {
this.expanded = !this.expanded;
console.log(this.expanded)
}
The issue im facing:
When I have multiple of this on the same page and I click one, they all seem to expand.
I cannot seen to get one to expand.
Edit:
The amount of collapsible links will be dynamic and will change as they are generated and pulled from the database. It could be one link today but 30 tomorrow etc... so having set variable names like expanded 1 or expanded 2 will not be viable
Edit 2:
Ok, so the full code for the click handler is like so:
toggleClass(event) {
event.stopPropagation();
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
console.log("contains class, remove it")
} else {
event.target.classList.add(className);
console.log("Does not contain class, add it")
}
}
and the code in the HTML is like so:
<div (click)="toggleClass($event)" class="collapsible-panel" *ngFor="let category of categories" >
<h3 class="collapsible-panel__title">{{ category }}</h3>
<ul class="button-list button-list--small collapsible-panel__content">
<div *ngFor="let resource of resources | resInCat : category">
<span class="underline display-block margin-bottom">{{ resource.fields.title }}</span><span class="secondary" *ngIf="resource.fields.description display-block">{{ resource.fields.description }}</span>
</div>
</ul>
</div>
you could apply your class through javascript
<div (click)="handleClick($event)">
some content
</div>
then your handler
handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
}
In plain html and js it could be done like this
function handleClick(event) {
const className = 'collapsible-panel--expanded';
if (event.target.classList.contains(className)) {
event.target.classList.remove(className);
} else {
event.target.classList.add(className);
}
console.log(event.target.classList.value);
}
<div onclick="handleClick(event)">
some content
</div>
Try to pass unique Id. (little modification)Ex: -
in component.ts file:
selectedFeature: any;
categories:any[] = [
{
id: "collapseOne",
heading_id: "headingOne",
},
{
id: "collapseTwo",
heading_id: "headingTwo",
},
{
id: "collapseThree",
heading_id: "headingThree",
}
];
toggleClass(category) {
this.selectedFeature = category;
};
ngOnInit() {
this.selectedFeature = categories[0]
}
in html:-
<div class="collapsible-panel" *ngFor="let category of categories">
<!-- here you can check the condition and use it:-
ex:
<h4 class="heading" [ngClass]="{'active': selectedFeature.id==category.id}" (click)="toggleClass(category)">
<p class="your choice" *ngIf="selectedFeature.id==category.id" innerHtml={{category.heading}}></p>
enter code here
-->
.....
</div>
Try maintaining an array of expanded items.
expanded = []; // take array of boolean
toggleClass(id: number) {
this.expanded[i] = !this.expanded[i];
console.log(this.expanded[i]);
}
Your solution will be the usage of template local variables:
see this: https://stackoverflow.com/a/38582320/3634274
You are using the same property expanded to toggle for all the divs, so when you set to true for one div, it sets it true for all the divs.
Try setting different properties like this:
<div (click)="toggleClass("1")" [class.collapsible-panel--expanded]="expanded1" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
<div (click)="toggleClass("2")" [class.collapsible-panel--expanded]="expanded2" class="collapsible-panel" *ngFor="let category of categories">
....
</div>
TS:
expanded1 = false;
expanded2 = false;
toggleClass(number:any) {
this["expanded" + number] = !this["expanded" + number];
console.log(this["expanded" + number])
}

How can I keep the same index of the initially rendered list when users clicked on a filtered item in React Webpack?

I have included a filter (filteredCat) for my catalogue and it works as expected visually.
The issue is that although the items are filtered, the index being logged doesn't reflect the index of the new filtered list. It returns the index of the originally rendered list.
For example if the list is filtered down to a single item whose index was initially 10. It still logs the index of 0.
export default class Catalogue extends React.Component{
productClickHandler(index){
console.log(index);
};
render(){
let filteredCat = this.props.DVDLibrary.filter(
(dvds) => {
return(
dvds.title.toLowerCase().indexOf(this.props.searchFilterInput) !== -1
)
}
);
var catologueList = filteredCat.map((dvds,index) => {
return(
<li key={index} onClick={this.productClickHandler.bind(this,index)}>
<div className="inner">
<h2>{dvds.title}</h2>
<img src={dvds.image}/>
<p><strong>Rating</strong>: {dvds.rating}</p>
<p><strong>Price</strong>: {dvds.price}</p>
<p><strong>Stock</strong>: {dvds.stock}</p>
</div>
</li>
)
});
return(
<div>
<ul>
{catologueList}
</ul>
</div>
)
}
}
How can I keep the same index of the initially rendered list when users clicked on a filtered item?
Referencing the filteredCat variable instead of the catologueList index works.

Categories