I have a list in a table with button to view a PDF requested from server and I want to make a spinner until the document is received.
I made it to work, but when I click on a button, the spinner appears on all buttons, not only the one I clicked.
Angular
<button (click)="preview(key)">
<div
*ngIf="!this.isLoading"
>
<mat-icon>visibility</mat-icon>
<p>View</p>
</div>
<mat-spinner
*ngIf="this.isLoading"
color="accent"
[diameter]="20"
></mat-spinner>
</button>
isLoading: boolean = false
preview(key: any) {
this.isLoading = true
}
isLoading goes 'false' in another method when the document is received.
you have to pass an id to the preview function so that you can track which row has the spinner, then you will set the isLoading to that value! (you can use the row's index context property)
<td mat-cell *matCellDef="let key = index">
<button (click)="loadingRow = key">
<div *ngIf="!(loadingRow == key)">
<mat-icon>visibility</mat-icon>
<p>View</p>
</div>
<mat-spinner *ngIf="loadingRow == key"
color="accent" [diameter]="20">
</mat-spinner>
</button>
</td>
loadingRow: number | null = null
then after the .pdf is loaded you have to return the flag 'loadingRow' to null
Related
Using Angular 13, I have been trying to add a menu using Angular MatMenu (https://material.angular.io/components/menu/overview) which is conditionally shown. Basically a bar shows button (operations) and some may have suboperations. In this case I want to display the suboperations when clicking on the button.
Here is the basic code I have, which is the complete html since there is another menu in the beginning. Note that removing it does not change the behavior.
<div class="rounded-box" *ngIf="!!selectedCategory">
<div class="selector" *ngIf="rateOrDislocationEnabled; else simpleAggregation">
<button color="primary" mat-button mat-icon-button [matMenuTriggerFor]="categoryMenu" class="dropdownButton">
<div class="dropdownText">{{ selectedCategory | agReplace: "_":" " }}</div>
<mat-icon class="dropdownIcon">arrow_drop_down</mat-icon>
</button>
<mat-menu #categoryMenu="matMenu" [overlapTrigger]="true" data-testid="categories-menu">
<button mat-menu-item *ngFor="let category of availableCategories" (click)="setSelectedCategory(category)">
<span [ngClass]="{ selectedLabel: isSelected(category) }">{{ category | agReplace: "_":" " }}</span>
<div *ngIf="category === BlockCategory.RATE_MAKING" class="alpha">ALPHA</div>
</button>
</mat-menu>
</div>
<ng-container *ngFor="let operationCategory of getOperations(); let lastItem = last">
<ng-container *ngFor="let operation of operationCategory">
<button *ngIf="operation.subOperations.length === 0"
mat-icon-button
class="iconWrapper"
id="operation-icon-{{ operation.value }}"
(click)="addOperation(operation.value)"
[disabled]="operation.disabled$ | async">
<mat-icon [ngClass]="operation.icon" class="icon" [agToolTip]="operation.tooltip$ | async"></mat-icon>
</button>
<ng-container *ngIf="operation.subOperations.length !== 0">
<button
mat-button
mat-icon-button
class="iconWrapper"
id="operation-menu-icon-{{ operation.value }}"
[matMenuTriggerFor]="subMenu">
<mat-icon [ngClass]="operation.icon" class="icon" [agToolTip]="operation.tooltip$ | async"></mat-icon>
</button>
<mat-menu #subMenu="matMenu">
<button mat-menu-item>Settings</button>
<button mat-menu-item>Log off</button>
</mat-menu>
</ng-container>
</ng-container>
<div class="divider" *ngIf="!lastItem"></div>
</ng-container>
</div>
I have created a stackblitz reproducing the issue: https://angular-ivy-cvv2xk.stackblitz.io
The issue is that when I click on the button, nothing happens. When I move the button out of the ngFor loops though, it works properly. I have tried things such as removing the ngIf condition (so all buttons are doubled) and none show the menu when clicking on it.
So the "submenu" is never displayed.
I wonder if I need to make the mat-menu specific or give it some id to ensure there are no conflicts? Since I'm a bit new to Angular I maybe be missing something.
Thanks in advance
Your button which triggers submenu got multiple button tags.
<button
mat-button
mat-icon-button
Can you remove one and try again?
Edit:
You use <ng-container *ngFor="let operationCategory of getOperations();
to populate your array.
If you press your button, angular will trigger change detection and this will trigger again your getOperations().
This will lead to this behaviour and dosent open your submenu.
So you should try to replace getOperations() with async pipe (if this is the case) or use properties.
card.vue is the template with the toggleLike method and the button tag
<template>
<div
class="p-24 grid grid-cols-1 sm:grid-cols-1 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-3 gap-12"
>
<div
v-for="movie in movies"
:key="movie.imdbID"
>
<card :movieData="movie" />
</div>
</div>
</template>
I have dynamically generated buttons, but whichever I click, every time the first one gets "selected" or diselected (depends on its previous state).
I want to toggle them independently.
<button #click="toggleLike()" class="mr-2">
<i id="but" class="fa-thumbs-up" :class="{ fas: isLiked, far: !isLiked }"></i>
toggleLike() {
if(this.movieData.likes == 0) {
$('#but').removeClass('far');
$('#but').addClass('fas');
this.movieData.likes+1;
}
else {
$('#but').removeClass('fas');
$('#but').addClass('far');}
return this.$store.dispatch("updateMovieLikes", {
imdbID: this.movie.imdbID,
});
},
You should put more code on how you structure your generated buttons, is that an array? If it is then your isLiked should be an array
<div v-for="movie in movies">
<button #click="toggleLike(movie)" class="mr-2">
<i id="but" class="fa-thumbs-up" :class="movie.likes === 0 ? 'fas' : 'far'"></i>
</button>
</div>
methods: {
toggleLike(movie) {
movie.likes = movie.likes === 0 ? 1 : 0;
this.$store.dispatch("updateMovieLikes", {
imdbID: this.movie.imdbID,
});
}
}
Edit: Just a tip you dont need jquery in vue
Welcome to SO, You could actually shift-away from jquery with vue. You already made a dynamic class binding on your but element therefore you can remove the jquery class handling on your method.
template:
<button #click="toggleLike()" class="mr-2">
<i class="fa-thumbs-up" :class="isLiked ? 'fas' : 'far'"></i>
</button>
method:
toggleLike() {
// Add here how you handle the toggling of isLiked data
if (this.movieData.likes === 0) this.movieData.likes++;
return this.$store.dispatch("updateMovieLikes", {
imdbID: this.movie.imdbID,
});
},
Note.
I'm not sure how you generate the buttons dynamically, can you show it on your question? (can't comment yet). You should also show how do you handle the toggling of your isLiked data.
I have Parent component with 2 different child, inquiryForm and inquiryResponse, I got situation when I need to hide and show this 2 component based on condition:
If user had click submit on inquiryForm, it will hide inquiryForm component and show inquiryResponse component
On inquiryResponse component, there are button display inquiry form, where user clicked it and will hide inquiryResponse component and show inquiryForm.I cant solved this.
I know it can be solved using router but I want different solution like using service or subject
this is demo I created using stackblitz, this is what I had tried;
inquiry-response.ts
getReceivedSummons() {
this.inquiryStore.summons$.subscribe(result => {
this.receivedSummon = result;
this.addCheckboxes();
this.isShowResponse = true;
});
}
showInquiryForm() {
// do something
}
inquiry-response.html
<div *ngIf="isShowResponse">
<p>Inquiry Response</p>
<form [formGroup]="form" (ngSubmit)="submitSelectedCheckboxes()">
<ng-container formArrayName="receivedSummons" *ngFor="let summon of formReceivedSummons.controls; let i = index">
<ng-container [formGroup]="summon">
<ng-container formArrayName="items" *ngFor="let item of formReceivedSummonsItems(i).controls; let j = index">
<ng-container [formGroup]="item">
<input type="checkbox" formControlName="isChecked"> {{item.value.name}}
</ng-container>
</ng-container>
</ng-container>
<div *ngIf="!summon.valid">At least one order must be selected</div>
</ng-container>
<br>
<span class="button">
<button [disabled]="!form.valid">submit</button>
</span>
<button (click)="showInquiryForm()"> ( change ID number ) display inquiry form</button>
</form>
</div>
As AJT_82 say, you app.component can be like
<app-inquiry-form *ngIf="step==1" (submit)="step=2"></app-inquiry-form>
<br>
<app-inquiry-response *ngIf="step==2" (click)="step=1"></app-inquiry-response>
//you has a variable
step:number=1;
And in each component
#Output() submit=new EventEmitter<any>()
..in somewhere...
this.submit.emit()
#Output() click=new EventEmitter<any>()
..in somewhere...
this.click.emit()
I have a Angular material table, each table row expands when it's click
within the last cell I load a component dynamically with ComponentFactory the loaded component is a dropdown.
The issue I'm having is when the dropdown is clicked the table row will expand and contract.
This is how I add function to the rows
<mat-header-row *matHeaderRowDef="displayedColumns"></mat-header-row>
<mat-row *matRowDef="let row; columns: displayedColumns;let index=index" (click)="expandRow(index, row)" #myRow></mat-row>
This is the cell, where user side menu is loaded in:
<ng-container *ngIf="column === 'viewSelection'">
<ng-container matColumnDef="viewSelection">
<mat-header-cell *matHeaderCellDef></mat-header-cell>
<mat-cell *matCellDef="let row">
<ng-container #sideMenu></ng-container>
<!-- <user-side-menu></user-side-menu> -->
</mat-cell>
</ng-container>
</ng-container>
How the user menu is loaded, which works fine:
let sideMenu = this.sideMenu.toArray()
for (let i = 0; i < sideMenu.length; i++) {
const factory = this.resolver.resolveComponentFactory(this.ellipsis);
let container = this.sideMenu.toArray()[i]
const ellipsisComponent = container.createComponent(factory);
ellipsisComponent.instance.data = this.returnedData[i]
console.log(container, ellipsisComponent.data);
Html for loaded user menu:
<a aria-expanded="false" aria-haspopup="true" class="btn btn-icon has-dropdown" data-toggle="dropdown" id="grid-menu">
<i class="ion-more"></i>
</a>
<div class="c-dropdown__menu dropdown-menu" aria-labelledby="grid-menu">
<a class="c-dropdown__item dropdown-item" (click)="viewUser({Id:data?.Id}, $event); $event.preventDefault()" >View/Edit User</a>
<a *ngIf="data?.IsActive" class="c-dropdown__item dropdown-item" (click)="activeDeactivateUser(data?.Id, false); $event.preventDefault()" data-toggle="modal" data-target="#deactivate-member">Deactivate User</a>
<a *ngIf="!data?.IsActive" class="c-dropdown__item dropdown-item" (click)="activeDeactivateUser(data?.Id, true); $event.preventDefault()" data-toggle="modal" data-target="#activate-member">Activate User</a>
</div>
As you can see I'm using prevent default, this does nothing, have tried
stop propagation but this stops the functions I want to execute from executing
Hopefully someone can point me in the right direction
You need to use stopPropagation() to stop the event from passing to parent DOM. You use preventDefault() to stop the default event action from being taken for the event object. For example, in the case of an anchor element, the default click action is to open the link, so if you wanted to implement a special check that prevents the link from being opened under a specific condition, you could use preventDefault() from within the (click) callback function to do that. But if you want to stop the click event from being passed to the parent element, you would use stopPropagation():
<span onclick="alert('You've already visited Stack Overflow')">
<a href="https://stackoverflow.com" (click)="visitStack($event)">
Stack Overflow
</a>
</span>
stackVisited = false;
visitStack(event) {
if (this.stackVisited) {
event.preventDefault();
} else {
this.stackVisited = true;
event.stopPropagation();
}
}
I am asking about hiding and showing an element in Vue.js
I always use this
<ele v-if="value" />
and then set {value} in Vue Instance data object, then toggle True/False for toggle visible, but now in my situation , my v-if condition put in some element , then this element create with v-for directive
some thing like this
<div v-for="item in items" >
<ele v-if="value" :key="item.i" />
<ele v-if="value" :key="item.i" />
<ele v-if="value" :key="item.i" />
// this button fire a method for Change (toggle) value (used for v-if)
<button #click="ToggleValue" > update </button>
</div>
In my view i have a table contain some rows and each rows have some field ( all field have v-if directive ) and in each rows we have button for fire method
Now what is my question ?!!
At the end my table is doing this , when click on every button ToggleValue method execute and toggle value of (value) object , now all field in all rows change the value ( all thing doing right :D )
but I want click on every button in each row just change the value of that row
I have dummy way
< ele v-if="value(item.id)" />
.........
.........
<button #click="ToggleValue(itme.id)" >
if my index of loop is Const and static I use this way , but now items in loop are dynamic
all thing was in my pen at here , thanks for give me your time
https://codepen.io/hamidrezanikoonia/pen/OQGrPB?editors=1100
Instead of having a single value, turn value into an object (or array) and index it by item.id.
Updated codepen: https://codepen.io/acdcjunior/pen/MQRZmK?editors=1010
In your pen, the JavaScript:
...
],
update_:false
},
methods: {
set_update() {
this.update_ = !this.update_;
}
}
becomes:
...
]
update_: {1: false, 2: false, 3: false}
},
methods: {
set_update(id) {
this.update_[id] = !this.update_[id];
}
}
And the template:
<td :key="getValue.id+4" v-if="update_" mode="in-out" > {{ getValue.rate_curr }} </td>
...
<button #click="set_update()" type="button" class="btn btn-primary"> Update </button>
becomes:
<td :key="getValue.id+4" v-if="update_[getValue.id]" mode="in-out" > {{ getValue.rate_curr }} </td>
...
<button #click="set_update(getValue.id)" type="button" class="btn btn-primary"> Update </button>