The refreshTable() function is in the parent component. I need to trigger it whenever I updated information in my modal and closes it.
I am using #ng-bootstrap for my modal
For may parent.component.ts
import { NgbModal } from '#ng-bootstrap/ng-bootstrap';
constructor(
private ngbModal: NgbModal,
)
viewModal() {
const openModal = this.ngbModal.open(ModalComponent);
openModal .componentInstance.id = row.id;
}
refreshTable() {
//refresh code block here
}
For my modal.component.ts
import { NgbActiveModal} from '#ng-bootstrap/ng-bootstrap';
constructor(
private activeModal: NgbActiveModal,
)
updateParent() {
//update data to database code here
this.activeModal.close();
}
How to trigger refreshTable() from ModalComponent after closing the modal? Since there are changes in the data from database, data from parent is not updated accordingly when modal is closed.
Try to change to this
const openModal = this.ngbModal.open(ModalComponent);
openModal.componentInstance.id = row.id;
openModal.dismissed.subscribe(
_ => {
this.refreshTable();
}
)
in parent.component.ts
Pass it from the parent component to the child component as a prop.
Use bind or an arrow function to preserve the this value.
Add an output event to your modal component
#Output() onCLose: EventEmitter<any> = new EventEmitter();
When the user clicks close just call the emit() method
this.onCLose.emit(value); //value can be whatever you want to pass to the parent
Update your viewModal method in the parent to include a subscription to the close event
viewModal() {
const openModal = this.ngbModal.open(ModalComponent);
openModal.componentInstance.id = row.id;
openModal.componentInstance.onClose.subscribe(value =>
// handle the close event
this.refreshTable();
})
}
You could also do it any other way communication between components can be achieved:
service
state management (ngrx, etc)
ngBootstrap modals have a beforeDismiss event, to which you can add logic before you close the modal. You have to return a truthy value for the modal to actually close.
You could do it like this:
const openModal = this.ngbModal.open(ModalComponent, { beforeDismiss: () => this.updateParent() }); // if modal is not closing, your function is returning false.
Check here for info on that function (do use ctrl F to find the option if needeD)
Edit: #tom-fong's answer is also a good solution, but you'd have to check if you're using version > 8.0.0
Related
I have a dialog message in a modal that asks the user to save changes if there is an attempt to navigate to another screen without saving.
It has 3 buttons (Save, Proceed without Saving, and Close). Obviously, the modal window looks exactly the same on all screens but the Save and Go back without Saving perform different actions depending on the screen.
I tried creating a separate Redux reducer for the modal but I learnt that you aren't allowed to store functions in Redux.
So my question is what is the recommended approach in a situation where you have the same modals in terms of UI but different in terms of the actions they perform?
I would really want to have it at the root level so that I can just dispatch an action and not be bothered with importing the modal to each component and controlling its state inside the component.
Here is an example of the action that I initially was planning to dispatch to show the modal
showModal: (state, { payload }) => {
state.modal.isOpen = true;
state.modal.header = payload.header;
state.modal.title = payload.title;
state.modal.btnText1 = payload.btnText1;
state.modal.btnText2 = payload.btnText2;
state.modal.btnText3 = payload.btnText3;
state.modal.btnAction1 = payload.btnAction1;
state.modal.btnAction2 = payload.btnAction2;
state.modal.btnAction3 = payload.btnAction3;
}
I would create a "wrapper" component like PageWithConfirmModal, and use it in each page component:
const MyCurrentPage = function( props ){
return <PageWithConfirmModal
showModal= { props.showModal }
save= { props.callSpecificSaveAction }
proceed= { props.callSpecificProceedAction }
close= { props.callSpecificCloseAction }
>
... page content ...
</PageWithConfirmModal>;
}
It depends where your payload.btnAction1 etc. come from, but you will figure out how to adapt my example, I guess.
Alternatively there are several possible variants, depending on your situation, e.g. pass some property that describes what to do instead of the actions, and decide which action to use inside the PageWithConfirmModal or inside the modal, e.g.:
const MyCurrentPage = function( props ){
return <PageWithConfirmModal
showModal= { props.showModal }
whatToDo= { DO_ACTIONS_FROM_MAIN_PAGE }
>
... page content ...
</PageWithConfirmModal>;
}
// e.g. inside PageWithConfirmModal.jsx
let actions;
if( DO_ACTIONS_FROM_MAIN_PAGE ){
actions = {
save: callSpecificSaveAction,
proceed: callSpecificProceedAction,
close: callSpecificCloseAction,
}
} else if( ...
Can't remove fullscreenchange event listener in ngOnDestroy Angular 6
I've tried calling the .removeEventListener() in ngOnDestroy which doesn't remove the events. I have also tried calling the removeEventListeners in a function after a 10 second timeout, and the events still continue to be triggered after.
imports
import { DOCUMENT } from '#angular/common';
import { Component, HostBinding, Inject, OnInit, OnDestroy } from '#angular/core';
Component code
elem: any;
constructor(private framesService: FramesService, private route: ActivatedRoute,
#Inject(DOCUMENT) private document: Document) { }
ngOnInit() {
this.elem = this.document.documentElement;
this.document.addEventListener('fullscreenchange', this.onFullscreenChange.bind(this));
this.document.addEventListener('mozfullscreenchange', this.onFullscreenChange.bind(this));
}
onFullscreenChange(event: Event) {
event.preventDefault();
console.log('Fullscreen event fired');
}
onViewFrame() {
if (this.elem.requestFullscreen) { // Chrome
this.elem.requestFullscreen();
} else if (this.elem.mozRequestFullScreen) { // Firefox
this.elem.mozRequestFullScreen();
}
}
ngOnDestroy() {
this.document.removeEventListener('fullscreenchange', this.onFullscreenChange.bind(this));
this.document.removeEventListener('mozfullscreenchange', this.onFullscreenChange.bind(this));
}
The onViewFrame() function is tied to a click event from a button on the page.
Every time this component is constructed, the events are added, but they are never removed. So if this component is loaded 3 times during a browsing session on the page, it will trigger the console.log three times every time full screen is initiated or the ESC key is used to exit full screen. Would like the events to be removed upon leaving so that they may be re-registered properly the next time.
this.onFullscreenChange.bind(this) creates a new reference to that function. You need to pass the same reference to addEventListener and removeEventListener.
elem: any;
fsEventHandler: any = this.onFullscreenChange.bind(this); // <- Here
ngOnInit() {
this.elem = this.document.documentElement;
this.document.addEventListener('fullscreenchange', this.fsEventHandler);
this.document.addEventListener('mozfullscreenchange', this.fsEventHandler);
}
ngOnDestroy() {
this.document.removeEventListener('fullscreenchange', this.fsEventHandler);
this.document.removeEventListener('mozfullscreenchange', this.fsEventHandler);
}
See MDN for more explanation.
Since you are using Angular you can use RxJS to achieve the same behavior.
You can create Observable using fromEvent
fromEvent(this.document, 'fullscreenchange');
To trigger some function you need to add .pipe() with tap operator, to activate it you also need to subscribe to it. Also save the subscription to be able to unsubscribe inside of ngOnDestroy()
ngOnInit() {
this.elem = this.document.documentElement;
console.log(this.document);
this.fullScreen = fromEvent(this.document, 'fullscreenchange').pipe(
tap(this.onFullscreenChange.bind(this))
).subscribe();
}
onFullscreenChange(event: Event) {
event.preventDefault();
console.log('Fullscreen event fired');
}
ngOnDestroy() {
this.fullScreen.unsubscribe();
}
In my component template I'm using AutoDeltaSettings Component which I want to show only If showAuto data attribute's of the component is true.
<auto-delta-settings v-if="showAuto"> </auto-delta-settings>
Here is the showAuto data attribute
data () {
return {
stepWidth: STEP_WIDTH,
headerHeight: HEADER_HEIGHT,
paddingTop: PADDING_TOP,
paddingBottom: PADDING_BOTTOM,
footerHeight: FOOTER_HEIGHT,
showAuto: false
}
}
I have registered the events on the created hook
created () {
EventBus.$on('protocol-changed', this.protocolChanged)
EventBus.$on('show-advanced-settings', this.showAdvancedSettings)
}
When show-advanced-settings event is emitted I'm calling showAdvancedSettings method of the component
methods: {
showAdvancedSettings (toShow) {
console.log("To Show - Before"+ this.showAuto)
this.showAuto = toShow
console.log("To Show - After"+ this.showAuto)
}
}
I'm emitting the event from another component on a click of a button
showAdvancedSettingsModal () {
console.log('Inside advanced settings opener - Before '+ this.showAdvancedSettings)
this.showAdvancedSettings = true
console.log('Inside advanced settings opener - After '+ this.showAdvancedSettings)
EventBus.$emit('show-advanced-settings', this.showAdvancedSettings)
},
But this only works when I click the button first time, in this case AutoDeltaSettings component shows up (which is a basically a modal), when I close the modal and click the button again to display the AutoDeltaSettings component it doesn't work.
I have read this post Conditional v-if is working only for the first time?
I want to be able to intercept an elements on click event that get's triggered by a React component and then override the functionality with native javascript.
window.addEventListener('load', function() {
var el = document.getElementById('button');
el.addEventListener('click', function(e) {
e.stopPropagation();
window.location.href = 'http://www.google.com';
});
});
The above code is placed after the bundled react code and it looks as if it can't find the element? Is there a way I can wait for React to be loaded? Or is there a better way I can handle this? Or can I override/attach an event to a react element? (I cant use react as it's already bundled)
Since your React app ends up being a JS file that you reference in your index.html file, that means if you reference a node element that's generated inside of it, jQuery can't see it because it was dynamically generated.
To solve this issue you can use two React concepts: lifecycle methods, and refs.
One, create a reference of the button you're aiming to listen globally, in the parent component of that button:
class ParentComponent extends React.Component {
render() {
return (
<button ref={(button) => this.button = button}>
Click this button
</button>
);
}
}
Two, create a componentDidMount life cycle method to make use of the ref you've just created:
class ParentComponent {
componentDidMount = () => {
this.button.addEventListener('click', function (e) {
e.preventDefault();
console.log('it works?!');
});
}
render() {
return (
<button ref={(button) => this.button = button}>
Click this button
</button>
);
}
}
Keep in mind that in other lifecycle methods like componentWillMount, or componetWillUpdate refs are either not existent or are old, because the component hasn't made it's most recent render yet. So, if you want to integrate 3rd party DOM libraries, use didMount.
I actually managed to figure it out using plain javascript, here's my code:
window.addEventListener('load', function() {
var checkExist = setInterval(function() {
var el = document.getElementById('button');
if (el != null) {
clearInterval(checkExist);
el.addEventListener('click', function(e) {
e.stopPropagation();
window.location.href = 'http://www.google.com';
});
}
}, 100);
});
import {inject, bindable, customElement} from 'aurelia-framework';
import {EventAggregator} from 'aurelia-event-aggregator';
#customElement('add-company-modal')
#inject(EventAggregator)
export class AddCompanyModal {
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
attached() {
console.log('attached add company modal');
this.eventAggregator.subscribe('add-company-modal-toggle', open => {
console.log('getting hit');
if (open) this.open();
else this.close();
});
}
open() {
this.activate = true;
}
close() {
this.activate = false;
}
}
Am I doing this correctly? Basically I have another view model which is publishing events. the console log is getting read out. The idea is that activate is bound to a class:
<div class="modal-backdrop ${activate ? 'modal-show' : ''}" click.trigger="close()">
<div class="modal">
<div class="modal-title">
<h1>Add Company <span class="modal-close">x</span></h1>
</div>
<div class="modal-body modal-no-footer">
<add-company>
</add-company>
</div>
</div>
</div>
This is wrapped in a template. However, this doesn't show at all. It works if I have a bindable property, but only when you click open on the other vm. When you close the modal you can't reopen it again, hence why I am using the event aggregator.
So the console.log is getting hit, I know that I have set up the event aggregation correctly, I just have a feeling that the framework isn't picking this up. I know if this was angular then it could be outside the digest cycle but I know aurelia doesn't work like that.
Reproduction
I've created a small repo to reproduce the issues on my Github
activate might sound better as activated or isActivated.
Just FYI there is a life-cycle method the router will call that is named activate so just trying to let you know of that possible collision.
If that doesn't help the next step for me would be to log the value of open inside the subscribe event. I would also verify that it's not failing with a method undefined error or anything.
Last, I personally like to define the properties either on the class or in the constructor. It's possible that you are binding to undefined so it doesn't catch the value change.
export class AddCompanyModal {
isActivated = false;
constructor(eventAggregator) {
this.eventAggregator = eventAggregator;
}
attached() {
console.log('attached add company modal');
this.eventAggregator.subscribe('add-company-modal-toggle', open => {
console.log('getting hit');
if (open) this.open();
else this.close();
});
}
open() {
this.isActivated = true;
}
close() {
this.isActivated = false;
}
}
The issue was with the value coming through to the subscribe callback was always false.
It was an error else where in my code.