Modal memory leak as more modals added - ngx-bootstrap

I am developing an Angular 4 application using ngx-bootstrap modals heavily. I am currently using the template + modalService way of opening up modals. During a click event, this line of code is called:
#ViewChild() worklistTemplate;
// ...
this.modalRef = this.modalService.show(this.worklistTemplate, this.config);
And the worklist template looks like this:
<ng-template #worklistTemplate>
<app-action-view
[leftButtons]="leftButtons"
[labelHeader]="modalHeader"
[labelIcon]="modalIcon"
[modalRef]="modalRef">
<div class="row modal-info-panel">
<div class="col-xs-4 modal-user-info">
<fa name="mars" class="gender-icon"></fa>
<div class="field-panel">
<input type="text"
[ngModel]="rowInfo.lastName"
[ngClass]="{ 'modal-editable-field': modalFieldsEditable }"
[disabled]="!modalFieldsEditable">
<input type="text"
[ngModel]="rowInfo.firstName"
[ngClass]="{ 'modal-editable-field': modalFieldsEditable }"
[disabled]="!modalFieldsEditable">
<div>
<label for="modal-mrn">MRN --</label>
<input id="modal-mrn" type="text"
[ngModel]="rowInfo.mrn"
[ngClass]="{ 'modal-editable-field': modalFieldsEditable }"
[disabled]="!modalFieldsEditable">
</div>
<div>
<label for="modal-dob">DOB --</label>
<input id="modal-dob" type="text"
[ngModel]="rowInfo.birthDate"
[ngClass]="{ 'modal-editable-field': modalFieldsEditable }"
[disabled]="!modalFieldsEditable">
</div>
<div class="edit-panel">
<fa *ngIf=modalFieldsEditable class="worklist-edit-buttons green-hover link" name="floppy-o" tooltip="Save" (click)=saveChanges()></fa>
<fa *ngIf=modalFieldsEditable class="worklist-edit-buttons red-hover link" name="times" tooltip="Cancel" (click)=toggleModalFields()></fa>
</div>
</div>
</div>
<div class="col-xs-8 modal-id-info">
Associated ID
<div class="full-width">
<div class="row" *ngFor="let i of rowInfo.associatedIDs">
<div class="col-xs-6 cell">{{i.id}}</div><div class="col-xs-6 cell">{{i.source}}</div>
</div>
</div>
</div>
</div>
<div class="row" id="modal-table">
<app-table-view
[columns]="objDetailsCols"
[tableData]="objDetailsData"
[expandTemplate]="rowImageContainer"
[expandColStyle]="expandColStyle">
</app-table-view>
</div>
<div *ngIf="resolvePanelVisible" class="resolve-panel"
[#slideRight]>
<div class="resolve-label">
<fa class="lt-icon" name="wrench"></fa>
Resolve QA Issue
</div>
<div class="resolve-body">
Hello, World!
</div>
<div class="resolve-footer">
<a>Validate</a>
<a>Resolve</a>
<a>Reload</a>
<a (click)="toggleResolvePanel()">Close</a>
</div>
</div>
</app-action-view>
</ng-template>
The modal can be closed by clicking outside of the modal boundaries or there is a button inside the ActionViewComponent that calls modalRef.hide().
Memory usage increases drastically as more and more modals are opened. In fact, if I open the modal around 5 times, the application becomes sluggish and almost unusable. This is especially apparent if there are many rows in our TableViewComponent.
Is there a problem with the way I am using the modals, or is this an issue related to ngx-bootstrap modals? OR, is the issue related to the browser and unavoidable? I am developing on Chrome 62 right now.
I have verified that onDestroy is never called on the TableViewComponent inside the modal. It is not even called if I navigate to a different page component, though another table (not in the template) does have onDestroy called when navigating from the page.
Any feedback is appreciated greatly- I have been stuck for way too long on this.

This is an issue of ngx-bootstrap, unfortunately. I created a pull request (https://github.com/valor-software/ngx-bootstrap/pull/3179) so this will be fixed as soon as my PR is merged and new version is released.

Related

Vue.js 3 and Modal with DaisyUI (Tailwind CSS)

I'm tinkering with DaisyUI within Vue.js 3 (porting an existing Vue+Bootstrap application to Tailwind CSS). I liked the idea that DaisyUI doesn't have JS wizardry going on behind the scenes, yet there seems to be some CSS voodoo magic that is doing things more complicated than they need to be (or at least this is my impression).
From the DaisyUI examples, here's the modal I'm trying to integrate:
<input type="checkbox" id="my-modal" class="modal-toggle"/>
<div class="modal">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label for="my-modal" class="btn">Yay!</label>
</div>
</div>
</div>
no javascript, yet the problem is that the modal will come and go according to some obscure logic under the hood that tests the value of the my-modal input checkbox at the top. That's not what I want. I want my modal to come and go based on my v-show="showModal" vue3 reactive logic!
Daisy doesn't seem to make that possible. Or at least not easily. What am I missing?
The modal is controller by the input field, so to open or close the modal just toggle the value of that input:
<template>
<!-- The button to open payment modal -->
<label class="btn" #click="openPaymentModal">open modal</label>
<!-- Put this part before </body> tag -->
<input type="checkbox" v-model="paymentModalInput" id="payment-modal" class="modal-toggle" />
<div class="modal modal-bottom sm:modal-middle">
<div class="modal-box">
<h3 class="font-bold text-lg">Congratulations random Internet user!</h3>
<p class="py-4">You've been selected for a chance to get one year of subscription to use Wikipedia for free!</p>
<div class="modal-action">
<label class="btn" #click="closePaymentModal">Yay!</label>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
const paymentModalInput = ref()
const openPaymentModal = () => {
paymentModalInput.value = true
}
const closePaymentModal = () => {
paymentModalInput.value = false
}
</script>

How to make Algolia instant search with Vue query records after page has loaded

I am working on a Gridsome application (SSG built with Vue). I added a search page and I've implemented the Algolia instant search with the autocomplete widget.
Gridsome by default lazy loads routes and renders the pages on the server-side, and since the instant search component I'm using does not support SSR, I wrapped it in a <ClientOnly> component.
The problem
When I navigate to /search it takes a while before I the components are rendered, this is because immediately I navigate to the page, the script to fetch the content is executed which blocks the rendering until it is finished. causing a bad UX.
The code
<div class="search-page-container">
<div class="search-field-container">
<h2><label for="search-field">Search</label></h2>
<ClientOnly>
<ais-instant-search :search-client="searchClient" :index-name="INDEX_NAME">
<ais-autocomplete>
<div slot-scope="{ currentRefinement, indices, refine }">
<input id="search-field" type="search" :value="currentRefinement" placeholder="what topic are you looking for?" #input="refine($event.currentTarget.value)" autocomplete="off" />
<div v-if="currentRefinement">
<h4>Articles</h4>
<div class="hits">
<ul v-for="index in indices" :key="index.label">
<li v-show="index.hits.length == 0"><em>No matches...</em></li>
<li>
<ul>
<li v-for="hit in index.hits" :key="hit.objectID">
<div>
<g-link :to="hit.slug"><ais-highlight attribute="title" :hit="hit"/></g-link>
<br>
<small class="post-description"><ais-highlight attribute="description" :hit="hit"/></small>
<!-- <hr> -->
</div>
</li>
</ul>
</li>
</ul>
</div>
</div>
<div class="space-top" v-else> <p> Type in the box to search for articles.. </p></div>
</div>
</ais-autocomplete>
</ais-instant-search>
</ClientOnly>
</div>
What I want
I want the queries to be made only when the user starts typing, that way, it doesn't block the rendering.

adding component on add button

i am a absolute beginner in vuejs,i have a feature of adding dynamic input fields on click of a button it will keep on adding rows and keeping in mind the counter should be incrementing also so that i can validate on backend, this is my code so far
<div id="settlement_container" class="container-fluid mt-4">
<div class="card rounded-0 shadow-lg">
<div class="card-body p-0">
<div class="card-header px-2">
<div class="row wow fadeIn">
<div class="col-5">
<h3>Add Store Status</h3>
</div>
</div>
</div>
<form class="custom-form-group" action="{{url('stores/addStoreStatusDB')}}" method="POST">
<div class="form-group col-6">
<label for="exampleInputEmail1">Tax</label>
<input type="text" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" name="tax" placeholder="Tax" required>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button #click="addstatus" class="btn btn-primary">Add Rows</button>
</div>
</div>
<div class="display-inline">
<div class="form-group col-md-6">
<button type="submit" class="btn btn-primary">Update Tax</button>
</div>
</div>
<dynamic-rows/>
</form>
</div>
</div>
</div>
{{-- Main layout --}}
#push('script')
<script src="{{ asset('js/app_vue.js') }}" ></script>
<script>
Vue.component('dynamic-rows',{
//accept data inside template
props:['counter'],
//accept data inside template
template:"<label for='exampleInputEmail1'>counter</label>"
});
const app = new Vue({
el: '#settlement_container',
data: {
counter:0
},
component:['dynamic-rows'],
methods:{
addstatus:function(e){
appendDiv=""
e.preventDefault();
alert("inside");
}
}
});
</script>
now i can do this in jquery in 5 minutes , but as i am beginner in vuejs i cant developer the sense of it of how to do it, i have a component and i want to repeat the component every time the button is clicked,
here is the fiddle! fiddle
OK, so a lot going on here and I think it may be easier to break down some of the points in isolation for you to play with and learn.
To add inputs, I think it makes more sense to have the values being in an array. Using Vue, you can iterate through that array to let each array element have its own <input/> while also simply adding another array element to add a new input:
<template>
<div>
<div v-for="(tax, index) in taxes" :key="index">
<input v-model="taxes[index]" />
</div>
<button type="number" #click="add">Add</button>
<p>Count: {{taxes.length}}</p>
</div>
</template>
<script>
export default {
data(): {
return {
taxes: [0]
}
},
methods: {
add() {
this.taxes.push(0);
}
}
});
</script>
Now with regards to the counter, I don't know what you mean validate on the backend. You could add a watcher on the taxes array and process changes there? Watchers are used sparingly, with computed properties being much preferred, but they may make sense if you need to be sending data to the backend instead of into the DOM.
The counter prop you registered in your code is not really going to work for the pattern I showed. Generally, props are for parent components to pass data to child components. The preferred pattern when sending data from child to parent is to use $emit. Read more here.

Vuejs Variables Not Displaying on Modal

I have a script that needs to output the results of an answer on a modal window. But currently it isn't rendering. The results output correctly outside of the modal HTML; even inspecting the modal source when it is visible shows the data isn't rendering; i.e. the results aren't hidden from view, they aren't calculated at all.
The full source is at
http://www.sic-parvis-magna.com/sicpm/mayan-vue/index.html
The variables in question are userAnswer, userAnswerDisplay, and userMessage
<div class="modal">
<div class="modal-background"></div>
<div class="modal-card">
<section class="modal-card-body">
<div v-if="userAnswer !== -1">
<div id="user-answer" v-html="userAnswerDisplay"></div>
<div id="user-answer-message" v-html="userMessage"></div>
<div id="user-hint"></div>
</div>
</section>
<footer class="modal-card-foot">
<button class="button is-success">Save changes</button>
<button class="button modal-close">Cancel</button>
</footer>
<button class="modal-close is-large" aria-label="close"></button>
</div>
</div>
The problem is your modal template is outside the Vue app (i.e., section#mainapp), so that template is not processed by Vue:
<section id="mainapp">...</section>
<div class="modal">...</div> <!-- FIXME: Move this into section above -->
Move the modal into section#mainapp for the computed properties to be evaluated.
demo

mat-accordion with-in component - angular 5

I have a question component with mat-accordion to display question. I'm calling my question component to question-list component.
Everything works great until this point, the issue arises when I'm clicking on question component it is getting open and remain in that state if I'm opening another one also. As per requirement, only one should open at a time.
I see an issue is with the accordion is reside in question component and from question-list, we are looping it with *ngFor so every accordion is having one expension-pannel in it.
My code was working until I had break mine code to two-component from one question-list(parent) and question(chile)
Sample Code:-
question-list.component:
<ng-template ngFor let-data [ngForOf]="questionList" let-i="index" >
<app-question [data]="data"></app-question>
</ng-template>
question.component:-
<mat-accodion>
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-description style="float:left;">
{{data.description}}
</mat-panel-description>
</mat-expansion-panel-header>
<form>
<div class="row text-left options">
<div class="col-md-6" *ngFor="let option of data.options;">
<mat-checkbox>{{option.description}}</mat-checkbox>
</div>
</div>
</form>
</mat-expansion-panel>
</mat-accodion>
A question is represented by a single expansion panel. A list of questions is represented by an accordion. So your question component should be an expansion panel only, not an accordion. Then you make the accordion part of the question list with the for loop inside the accordion.
Question List:
<mat-accordion>
<ng-container *ngFor="let data of questionList; let i = index">
<app-question [data]="data"></app-question>
</ng-container>
</mat-accordion>
Question:
<mat-expansion-panel>
<mat-expansion-panel-header>
<mat-panel-description style="float:left;">
{{data.description}}
</mat-panel-description>
</mat-expansion-panel-header>
<form>
<div class="row text-left options">
<div class="col-md-6" *ngFor="let option of data.options;">
<mat-checkbox>{{option.description}}</mat-checkbox>
</div>
</div>
</form>
</mat-expansion-panel>
#G. Tranter thanks, for your answer, actually this(your suggestion) I already tried and did not got any success, after that I tried the traditional way of doing collapse and expand with self-declared property in to every question object "expanded" by default this will be false to every question and we have used it as ngModel in expansion control, on change the state of one we handle other "expanded" property.
sample here which is working now,
<mat-expansion-panel [(ngModel)]="data.expanded"
name="fieldName" ngDefaultControl (opened)="panelOpenState(data._id)"
[expanded]="data.expanded">
<mat-expansion-panel-header>
<mat-panel-description style="float:left;">
{{data.description}}
</mat-panel-description>
</mat-expansion-panel-header>
<form>
<div class="row text-left options">
<div class="col-md-6" *ngFor="let option of data.options;">
<mat-checkbox>
{{option.description}}
</mat-checkbox>
</div>
</div>
</form>