I'm working in Angular 8. I'm using NG-BOOTSTRAP for styling.
In several of my components I offer the ability to click a delete button on an item, this brings up a modal window with a YES or NO and when YES is clicked, the modal closes and the route appears to refresh, no actual browser refresh - this is what I want. The list is updated correctly and all seems fine. Then, when I try and click on any other route in my navbar they all fail and the page stays where it's at until I refresh the browser page - also, the link in the URL bar isn't updating, which I suspect is causing the pages not to be able to be routed to. Not sure why this behavior is happening. Frustrating too. Looking for some assistance if possible. Thanks.
THIS IS THE HTML TABLE
<tbody>
<tr *ngFor="let client of clients">
<td>{{ client.name | titlecase }}</td>
<td>{{ client.website }}</td>
<td>{{ client.phone }}</td>
<td>{{ client.address.street | titlecase }}, {{ client.address.city | titlecase }}
{{ client.address.state | uppercase }}
{{ client.address.zip }}</td>
<td>
<button class="btn btn-primary" (click)="editClient(client._id)">
<fa-icon [icon]="faEdit"></fa-icon>
</button>
<button class="btn btn-danger ml-3" (click)="open(content, client)">
<fa-icon [icon]="faTrashAlt"></fa-icon>
</button>
</td>
</tr>
</tbody>
----- THIS IS THE MODAL TEMPLATE (SAME HTML PAGE)------
<!-- MODAL TEMPLATE -->
<ng-template #content let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Delete Client?</h4>
<button type="button" class="close" aria-label="Close" (click)="modal.dismiss('Cross click')">
<span aria-hidden="true">×</span>
</button>
</div>
<div class="modal-body">
<div class="row">
<div class="col-sm">
<button class="btn btn-success mr-3" (click)="deleteClient(modalContent._id)">YES</button>
<button class="btn btn-danger" (click)="modal.close('Close click')">NO</button>
</div>
</div>
</div>
</ng-template>
----- THIS IS THE TS FILE -----
deleteClient(id) {
this._clientService.deleteClient(id).subscribe(
response => {
console.log(response['message']);
// Close the modal window and reload the component
this._modalService.dismissAll();
this.reloadComponent();
},
error => console.log(error['message'])
);
}
///// MODAL FUNCTIONS
open(content, client) {
this.modalContent = client;
this._modalService
.open(content, { ariaLabelledBy: 'modal-basic-title' })
.result.then(
result => {
this.closeResult = `Closed with: ${result}`;
},
reason => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
}
);
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
///// FUNCTION TO RELOAD PAGE AFTER DELETE /////
reloadComponent() {
this._router.routeReuseStrategy.shouldReuseRoute = () => false;
this._router.onSameUrlNavigation = 'reload';
this._router.navigate(['admin/clients']);
}
Instead of reloading the page you could re-execute the call that binds the results from the backend to your clients var. This is at least a nice separation of sources & routing and can avoid further complications.
Something like:
deleteClient(id) {
this._clientService.deleteClient(id).subscribe(
response => {
console.log(response['message']);
// Close the modal window and reload the component
this._modalService.dismissAll();
this.getClients();
}, error => console.log(error['message'])
});
getClients() {
this._clientService.getClients().subscribe(
response => {
this.clients = response.data;
}, error => console.log(error['message'])
});
Related
below image is my JSON structure that came from MYSQL and retrieve using NodeJS and I'm rendering it to my VueJS as of now.
Here is the video of my error/bug. As you can see, it shows all the QR of each user even though I only click one.
It gives me the URL for each person in the table
I only need to show per user.
Below code is working fine until I inserted this code in my modal ":value="result.url" :size="size" level="H"> though it still works but not what I want.
<tbody>
<tr v-for="result in filteredPeople" :key="result.id">
<td>{{result.Memb_ID}}</td>
<th>{{result.First_Name}}</th>
<th>{{result.Middle_Name}}</th>
<th>{{result.Last_Name}}</th>
<th>{{result.Address}}</th>
<div class="btn">
<button type="button" class="btn btn-success">Edit Details</button>
<b-button v-b-modal.modal-1 class="btn btn-danger">Show QR</b-button>
</div>
<b-modal id="modal-1" title="QR">
<div class="showQR text-center">
<qrcode-vue :value="result.url" :size="size" level="H"/>
</div>
</b-modal>
</tr>
</tbody>
<script>
import axios from "axios";
import QrcodeVue from "qrcode.vue";
export default {
data() {
return {
search: "",
value: "",
size: 250,
results: {}
};
},
methods: {
getUsers() {
axios
.get("http://localhost:9000/api/users/")
.then(response => (this.results = response.data))
.catch(error => console.log(error));
}
},
computed: {
filteredPeople() {
if (this.search) {
return this.results.filter(result => {
return result.First_Name.startsWith(this.search);
});
} else {
return this.results;
}
}
},
components: {
QrcodeVue
}
};
</script>
You should have different modal's id for each item in v-for
<div class="btn">
<button type="button" class="btn btn-success">Edit Details</button>
<b-button v-b-modal="'modal-' + result.Memb_ID" class="btn btn-danger">Show QR</b-button>
</div >
<b-modal :id="'modal-' + result.Memb_ID" title="QR">
<div class="showQR text-center">
<qrcode-vue : value="result.url" :size="size" level="H"/>
</div>
</b-modal>
I would suggest to use only one modal and change the content based on a click event. this helps improve performance of the for loop:
<tbody>
<tr v-for="result in filteredPeople" :key="result.id">
<td>{{result.Memb_ID}}</td>
<th>{{result.First_Name}}</th>
<th>{{result.Middle_Name}}</th>
<th>{{result.Last_Name}}</th>
<th>{{result.Address}}</th>
<div class="btn">
<button type="button" class="btn btn-success">Edit Details</button>
<b-button v-b-modal.modal-1 class="btn btn-danger" #click="updateModalContent(result)">Show QR</b-button>
</div>
</tr>
<b-modal id="modal-1" title="QR">
<div class="showQR text-center">
<qrcode-vue :value="selectedResult.url" :size="size" level="H"/>
</div>
</b-modal>
</tbody>
<script>
export default {
data() {
return {
search: "",
value: "",
size: 250,
results: {},
selectedResult: {}
};
},
methods: {
updateModalContent(result) {this.selectedResult = result},
// Other Methods
},
};
</script>
I have created a modal to display information about a specific record when I click on the button.
{% for seat in seats %}
<span class="fa-stack fa-lg seat" id="{{ seat.id }}" data-toggle="modal" data-target="#seatModal">
<i style="color: lightgrey;" class="fa fa-stop fa-stack-2x"></i>
<strong style="color: white;" class="fa-stack-1x">
{{ seat.seatNo }}
</strong>
</span>
{% endfor %}
<div class="modal fade seat-details" id="seatModal" tabindex="-1" role="dialog" aria-hidden="true">
<div class="modal-dialog" role="document">
<div class="modal-content"></div>
</div>
</div>
I have an Ajax request that get the information for the specific seat when clicking on that seat icon.
$(document).ready(function () {
$('.seat').click(function() {
var url = Routing.generate('show_seat', {'seat': $(this).attr('id')});
$.get(url, function (data) {
$(".modal-content").html(data);
});
});
});
This ajax call perform a request to the 'show_seat' route which lead to this controller action:
public function showSeatAction(Seat $seat)
{
return $this->render('AppBundle:Seat:seat_details.html.twig', [
'seat' => $seat,
]);
}
This action renders the details in the seat_details.html.twig;
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">×</span></button>
<h4 class="modal-title">Seat Details</h4>
</div>
<div class="modal-body table-responsive no-padding">
<table class="table table-modal">
<tbody>
<tr>
<th>Description</th>
<td>{{ seat.desc }}</td>
</tr>
<tr>
<th>Seat Number</th>
<td>{{ seat.seatNo }}</td>
</tr>
<tr>
<th>Status</th>
<td>
{% if seat.status == 'special' %}
<span class="label label-info">{{ seat.status|upper }}</span>
{% elseif seat.status == 'bad' %}
<span class="label label-warning">{{ seat.status|upper }}</span>
{% else %}
<span class="label label-default">{{ seat.status|upper }}</span>
{% endif %}
</td>
</tr>
</tbody>
</table>
</div>
And finally when the Ajax request is done it loads it into the modal as you can see in that Ajax function: $(".modal-content").html(data);.
This works good, but now I'm trying to add a spinner, because what happens now is when I click one the modal opens and the right data displays, but when I close it and open a new one the model opens with the previous data and then when the Ajax is done the text data get replaced in the modal.
But I want to let a spinner show until the Ajax is done and then the complete modal should display.
I've tried to add this:
$(document).on({
ajaxStart: function() { $('.loading').show() },
ajaxStop: function() { $('.loading').hide() }
});
This makes the spinner appear when clicking and hides it when Ajax is done, but the modal still appear too early. So I've tried to modify it to this:
$(document).on({
ajaxStart: function() {
$('.loading').show();
$('.seat-details').hide();
},
ajaxStop: function() {
$('.loading').hide();
$('.seat-details').show();
}
});
But this doesn't change anything.
Can somebody help me out to fix this?
var seatModal = $("#seatModal");//your modal
$(document).on({
ajaxStart: function() {
seatModal.find(".modal-content").html("");//empty modal every ajaxstart
$('.loading').show();
seatModal.modal("hide");//hide
},
ajaxStop: function() {
$('.loading').hide();
seatModal.modal("show");//modal show
}
});
You can process with an other way. Initialize your modal with nothing in it.
Then, when the user close the modal, remove the content and replace it with your spinner.
$(".modal").on("hidden.bs.modal", function(){
$(".modal-content").html([your spinner here]);
});
I have been working on an Angular app and I'm stuck on something. I iterate through a list of objects using ng-repeat and have access to this variable ._id. Here's the markup of the parent page: when I delete button gets clicked, I want to pass the ._id to the parent's page (current page) controller so that it could be passed to the Modal window's controller
<tr ng-repeat="bucket in buckets">
<td>{{ bucket.name }}</td>
<td>{{ bucket._id }}</td>
<td class="col-sm-1">
<!-- View Bucket Page -->
<a ui-sref="bucketPage({ bucket_id: bucket._id })" class = "btn btn-primary">View</a>
<!-- Delete Bucket -->
<!-- <a ui-sref="userProfile({ bucket_id: bucket._id })" ng-click="deleteBucketModalWindow('sm')" class="btn btn-danger">Delete</a> -->
<a ng-click="deleteBucketModalWindow('sm')" href="#/" class="btn btn-danger">Delete</a>
</td>
</tr>
I get how to do it when I go to a different page (instead of modal). For that I do this:
<a ui-sref="userProfile({ user_id: user._id })" class = "btn btn-primary">View Profile</a>
Here's the controller of parent page:
// Modal window to delete a Bucket
$scope.deleteBucketModalWindow = function(size){
var user_id = $stateParams.user_id;
var bucket_id = $stateParams.bucket_id;
console.log("bucket id in userProfileCtrl: ", user_id);
console.log("bucket id in userProfileCtrl: ", bucket_id);
var modalInstance = $modal.open({
templateUrl: 'views/deleteBucketModal.html',
controller: 'deleteBucketModalCtrl',
size: size,
bucket_id: bucket_id,
resolve: {
bucket_id: function () {
return $stateParams.bucket_id;
}
}
});
modalInstance.result.then(function(){
// Refresh the data
User.getSingleUserDetails($stateParams.user_id)
.success(function(buckets){
$scope.buckets = buckets;
})
.catch(function(response){
console.log(response);
});
}, function(){
});
};
and here's the controller for the Modal window
angular.module('ResourceBucket')
.controller('deleteBucketModalCtrl', function($scope, $modalInstance, $auth, $http, $window, $rootScope, Bucket){
// Delete the bucket
$scope.deleteBucket = function(){
// close the modal
$modalInstance.close();
console.log("Inside deleteModal - bucket_id: ", $scope.bucket_id);
// bucket_id gets passed through the "resolve"
Bucket.deleteBucket($scope.bucket_id)
.then(function(data){
console.log("Just deleted a bucket!", data);
})
.catch(function(response){
console.log("In catch of deleteBucketModalCtrl: ", response);
});
};
$scope.cancel = function(){
// Just close the modal, dont do anything else
// After you close the modal, in .then() refresh the data
$modalInstance.close();
}
});
and here's the markup for the Modal window
<!-- Add Bucket Modal -->
<div controller="deleteBucketModalCtrl">
<div class="modal-header">
</div>
<div class="modal-body">
<ul>
<!-- Bucket Modal -->
<div class = "row">
<!-- Heading -->
<h2 class="text-center">Sure, you want to delete the bucket?</h2>
<button class="btn btn-danger" ng-click="deleteBucket()">Delete</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
</ul>
</div>
<div class="modal-footer">
</div>
</div>
Thank you!
Sorry, actually I figured it out what I was doing wrong. In the markup, you could pass a parameter using ng-click like the following:
<a ng-click="deleteBucketModalWindow('sm', bucket._id)" href="" class="btn btn-danger">Delete</a>
I have an angular app where i have a custom filter defined as sumCompletedCredits. Here is the html of the call:
{% if node.is_leaf_node %}
<span class="badge badge-success"
ng-bind="mpttdetails | filter:{category.id:{{node.id}}} | sumCompletedCredits">
</span>
<div class="row">
<button class="btn btn-default btn-sm pull-right"
ng-click="open({{node.id}}, {{node.min_credit}})">Add Course <span class="glyphicon glyphicon-plus"></span>
</button>
</div>
{%endif%}
Here is the custom filter definition:
app.filter('sumCompletedCredits', function () {
return function (mpttdetails) {
if(typeof mpttdetails === 'undefined'){
return;
}
else {
var sum = 0;
mpttdetails.forEach(function (detail) {
sum += detail.student_academic_credit.credit;
});
return sum;
}
};
});
Here is what i want to achieve: I would like to pass a 3rd parameter in the button's ng-click() function where the parameter would be the value from the above span like the follows:
ng-click="open({{node.id}}, {{node.min_credit}}, *{{{this content will be the value in the badge class from the custom filter}}}*)"
So basically i am trying to send the value that will be displayed in the span badge just before the button and send that value as the third parameter.How do i send the third detail?
You can assign the result of the filtering to a scope property and reference that property in your function:
<span ...
ng-bind="summed=(mpttdetails | filter:{category.id:{{node.id}}} |
sumCompletedCredits)">
</span>
...
<button ...
ng-click="open({{node.id}}, {{node.min_credit}}, summed)">
...
</button>
I've be looking for how to execute this but I can't find anything related so far, :(
I could nest both functions yes but I'm just wondering if this is possible?
I'd like to do this literally:
<td><button class="btn" ng-click="edit($index) open()">Open me!</button></td>
My JS code at the moment:
$scope.open = function () {
$scope.shouldBeOpen = true;
};
$scope.edit = function(index){
var content_1, content_2;
content_1 = $scope.people[index].name;
content_2 = $scope.people[index].age;
console.log(content_1);
};
I'd like to call two functions with just one click, how can I do this in angularJS?
I thought it'd be straight forward like in CSS when you add multiple classes...but it's not :(
You have 2 options :
Create a third method that wrap both methods. Advantage here is that you put less logic in your template.
Otherwise if you want to add 2 calls in ng-click you can add ';' after edit($index) like this
ng-click="edit($index); open()"
See here : http://jsfiddle.net/laguiz/ehTy6/
You can call multiple functions with ';'
ng-click="edit($index); open()"
A lot of people use (click) option so I will share this too.
<button (click)="function1()" (click)="function2()">Button</button>
The standard way to add Multiple functions
<button (click)="removeAt(element.bookId); openDeleteDialog()"> Click Here</button>
or
<button (click)="removeAt(element.bookId)" (click)="openDeleteDialog()"> Click Here</button>
Try this:
Make a collection of functions
Make a function that loops through and executes all the functions in the collection.
Add the function to the html
array = [
function() {},
function() {},
function() {}
]
function loop() {
array.forEach(item) {
item()
}
}
ng - click = "loop()"
Follow the below
ng-click="anyFunction()"
anyFunction() {
// call another function here
anotherFunction();
}
<!-- Button trigger modal -->
<button type="button" (click)="open(content)" style="position: fixed; bottom: 0; right: 130px;"
class="btn col-sm-1 btn-Danger" >
Reject
</button>
<ng-template #content let-modal>
<div class="modal-header">
<h4 class="modal-title" id="modal-basic-title">Profile update</h4>
<button type="button" class="btn-close" aria-label="Close" (click)="modal.dismiss('Cross click')"></button>
</div>
<div class="modal-body">
<div class="mb-3">
<label class="bg-danger text-light" for="Reject">Reason For reject</label>
<textarea matInput placeholder=" Reject" [(ngModel)]="asset_note">{{note}}</textarea>
</div>
</div>
<div class="modal-footer">
<!-- -->
<button type="button" class="btn btn-outline-dark" (click)="reject();modal.close('Save click') ">Save</button>
</div>
</ng-template>
**.ts file**
open(content: any) {
this.modalService.open(content, {ariaLabelledBy: 'modal-basic-title'}).result.then((result) => {
this.closeResult = `Closed with: ${result}`;
}, (reason) => {
this.closeResult = `Dismissed ${this.getDismissReason(reason)}`;
});
}
private getDismissReason(reason: any): string {
if (reason === ModalDismissReasons.ESC) {
return 'by pressing ESC';
} else if (reason === ModalDismissReasons.BACKDROP_CLICK) {
return 'by clicking on a backdrop';
} else {
return `with: ${reason}`;
}
}
close()
{
this.getDismissReason(ModalDismissReasons.ESC);
}
Which of the following is best practice (option1 or option2)
<button (click)="removeAt(element.bookId); openDeleteDialog()"> Click Here
<button (click)="removeAt(element.bookId)" (click)="openDeleteDialog()"> Click Here
ng-click "$watch(edit($index), open())"