Angular - remove LocationStrategy onPopState call back - javascript

I am using locationStrategy.onPopState to handle when users click on back button.
The logic is working fine, but I got a problem.
The callback is triggered even the component don't exist anymore (by changing the route or using *ngIf).
The point seems like the angular stay a reference of the component in the LocationStragy, like the commom problem with Observables without unsubscribe.
How can I release the callback function passed to locationStrategy on ngOnDestroy?
import { LocationStrategy } from '#angular/common';
...
constructor(private location: LocationStrategy) {}
...
protected preventBackButton() {
this.location.onPopState(() => {
if (this.stepper.selectedIndex > 0) {
if (this.stepper.selectedIndex > this.MIN_STEP_TO_PREVENT_BACK_BUTTON) {
this.preventChangePage();
}
this.stepper.previous();
}
});
}
ngOnDestroy(){
//release callback function here
}

I got the solution doing this:
1 - Change the angular locationStrategy.onPopState by window.addEventListener('popstate', ...);
2 - create a class arrow function variable to wrap your logic method
3 - Pass your arrow function to your listener: window.addEventListener('popstate', this.callBackEventHandle);
4 - On ngOnDestroy, remove the listener: window.removeEventListener('popstate', this.callBackEventHandle);
...
protected preventBackButton() {
window.addEventListener('popstate', this.callBackEventHandle);
}
callBackEventHandle = () => {
this.handleBackButton();
};
handleBackButton() {
if (this.stepper.selectedIndex > 0) {
if (this.stepper.selectedIndex > this.MIN_STEP_TO_PREVENT_BACK_BUTTON) {
this.preventChangePage();
}
this.stepper.previous();
}
}
...
ngOnDestroy() {
window.removeEventListener('popstate', this.callBackEventHandle);
}

Related

Javascript method to be able see if a value changes without re rendering the page

I am trying to be able to read a value that is boolean to see if a user did a specific action or not and I am using the ReactJS functional component style. I am trying to read the runValue in my code to see if the run() method changed the value and I want to be able to read this value without recalling the function.
I want to be able to put in my useEffect method this line of code;
Run.RunFunction().run((index) => {
if (index) {
\\\ do stuff here if index is true
} else {
///if index is false
}
}
my code
const Run = {
RunFunction: () => {
let runValue = false;
return {
run() {
runValue = true
},
listener: function(val) {},
checkStatus: function(listen) {
this.listener = listen
}
}
},
}
Run.RunFunction().checkStatus((index) => {
if (index) {
console.log('running')
} else {
console.log('not running')
}
});
I am having trouble having this code to work and I want to be able to see the value of the runValue initially and if it changes.
Thank you

React window unload event doesn't fire

I need to use navigator.sendBeacon() on window unload in order to let my server know the client has closed his window. I have searched everywhere and it just doesn't work for me.
For reference, the solution in this post didn't work either.
I have an App component that wraps my entire project. I am trying to set the unload event on it's componentDidMount() lifecycle method, and it just won't fire.
componentDidMount() {
window.addEventListener("beforeunload", this.unload);
}
componentWillUnmount() {
window.addEventListener("beforeunload", this.unload);
}
unload(e) {
e.preventDefault();
e.returnValue = 'test';
navigator.sendBeacon(`http://localhost:8080/window-closed/${this.props.username}`);
return 'test';
}
I expect the server to get the AJAX call, and the window to prompt the user 'test' before the window is closed. What actually happens is the window just closes as usual.
NOTE: the return 'test' & e.returnValue = '' statements are purely for testing. I'm only interested in the AJAX request.
Any help would be much appreciated.
If you're using a functional component, you can try this:
useEffect(() => {
window.addEventListener("beforeunload", handleUnload);
return () => {
window.removeEventListener("beforeunload", handleUnload);
};
}, []);
const handleUnload = (e) => {
const message = "o/";
(e || window.event).returnValue = message; //Gecko + IE
return message;
};
You should bind this to the unload method or transform it to arrow function.
Binging way
constructor() {
super();
this.state = {
//stuff
};
this.unload.bind(this);
}
componentDidMount() {
window.addEventListener("beforeunload", this.unload);
}
componentWillUnmount() {
window.removeEventListener("beforeunload", this.unload);
}
unload(e) {
navigator.sendBeacon(`http://localhost:8080/window-closed/${this.props.username}`);
}
Arrow functions way:
constructor() {
super();
this.state = {
//stuff
};
}
componentDidMount() {
window.addEventListener("beforeunload", this.unload);
}
componentWillUnmount() {
window.removeEventListener("beforeunload", this.unload);
}
unload = (e) => {
navigator.sendBeacon(`http://localhost:8080/window-closed/${this.props.username}`);
}
Remember to remove the eventlistener on componentWillUnmount (you are currently adding it again).
You may be able to use navigator.sendBeacon.
const UnloadBeacon = ({
url,
payload = () => {},
children
}) => {
const eventHandler = () => navigator.sendBeacon(url, payload())
useEffect(() => {
window.addEventListener('unload', eventHandler, true)
return () => {
window.removeEventListener('unload', eventHandler, true)
}
}, [])
return children
}
full example here: https://gist.github.com/tmarshall/b5433a2c2acd5dbfc592bbc4dd4c519c
Have you tried declaring upload function as a fat arrow function? Also declare it before componentDidMount. (for better readability) before passing it as a reference.
Also have you tried attaching listener in contructor ? And make surw to bind your function in constructor. For reference
Also destroy the listener at componentWillUnmount, instead of adding it. (useless) use reference to listener, to destroy. Which you will create in constructor.
Best of luck
I am unsure why beforeunload is not working, but as a workaround, you may consider using the hashchange event.
componentDidMount() {
window.addEventListener("hashchange", this.doSomething, false);
}
componentWillUnmount() {
window.removeEventListener("hashchange", this.doSomething, false);
}

VueJS how to use _.debounce on data changes

I'm building a little vue.js-application where I do some post requests. I use the watch-method to whach for api changes which then updates the component if the post request is successfull. Since the watcher constantly checks the API I want to add the ._debounce method but for some reason it doesn't work.
here is the code:
<script>
import _ from 'lodash'
export default {
data () {
return {
cds: [],
cdCount: ''
}
},
watch: {
cds() {
this.fetchAll()
}
},
methods: {
fetchAll: _.debounce(() => {
this.$http.get('/api/cds')
.then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
created() {
this.fetchAll();
}
}
</script>
this gives me the error: Cannot read property 'get' of undefined
Can someone maybe tell me what I'm doing wrong?
EDIT
I removed the watch-method and tried to add
updated(): {
this.fetchAll()
}
with the result that the request runs in a loop :-/ When I remove the updated-lifecycle, the component does (of course) not react to api/array changes... I'm pretty clueless
Mind the this: () => { in methods make the this reference window and not the Vue instance.
Declare using a regular function:
methods: {
fetchAll: _.debounce(function () {
this.$http.get('/api/cds/add').then(response => {
this.cds = response.body
this.cdCount = response.body.length
})
})
},
Other problems
You have a cyclic dependency.
The fetchAll method is mutating the cds property (line this.cds = response.body) and the cds() watch is calling this.fetchAll(). As you can see, this leads to an infinite loop.
Solution: Stop the cycle by removing the fetchAll call from the watcher:
watch: {
cds() {
// this.fetchAll() // remove this
}
},

How to fire an event on Vue switch change

I have a Vue component that has a vue-switch element. When the component is loaded, the switch has to be set to ON or OFF depending on the data. This is currently happening within the 'mounted()' method. Then, when the switch is toggled, it needs to make an API call that will tell the database the new state. This is currently happening in the 'watch' method.
The problem is that because I am 'watching' the switch, the API call runs when the data gets set on mount. So if it's set to ON and you navigate to the component, the mounted() method sets the switch to ON but it ALSO calls the toggle API method which turns it off. Therefore the view says it's on but the data says it's off.
I have tried to change the API event so that it happens on a click method, but this doesn't work as it doesn't recognize a click and the function never runs.
How do I make it so that the API call is only made when the switch is clicked?
HTML
<switcher size="lg" color="green" open-name="ON" close-name="OFF" v-model="toggle"></switcher>
VUE
data: function() {
return {
toggle: false,
noAvailalableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring }
},
mounted() {
this.toggle = this.isBeingMonitored;
},
watch: {
toggle: function() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
})
} else {
this.toggle = false;
this.noAvailalableMonitoring = true;
}
}
}
I would recommend using a 2-way computed property for your model (Vue 2).
Attempted to update code here, but obvs not tested without your Vuex setup.
For reference, please see Two-Way Computed Property
data: function(){
return {
noAvailableMonitoring: false
}
},
computed: {
report() { return this.$store.getters.currentReport },
isBeingMonitored() { return this.$store.getters.isBeingMonitored },
availableMonitoring() { return this.$store.getters.checkAvailableMonitoring },
toggle: {
get() {
return this.$store.getters.getToggle;
},
set() {
if(this.availableMonitoring) {
let dto = {
reportToken: this.report.reportToken,
version: this.report.version
}
this.$store.dispatch('TOGGLE_MONITORING', dto).then(response => {
}, error => {
console.log("Failed.")
});
} else {
this.$store.commit('setToggle', false);
this.noAvailableMonitoring = true;
}
}
}
}
Instead of having a watch, create a new computed named clickToggle. Its get function returns toggle, its set function does what you're doing in your watch (as well as, ultimately, setting toggle). Your mounted can adjust toggle with impunity. Only changes to clickToggle will do the other stuff.

Meteor React renders twice after subscription are ready

I'm using Meteor with react and FlowRouter to handle subscriptions. I find that when my component renders it will render twice after a few seconds, but only when I have the meteor mixin subscribed to a subscription.
For example:
PeoplePage = React.createClass({
displayName:"People",
mixins: [ReactMeteorData],
getMeteorData() {
const subHandles = [
Meteor.subscribe("allPeople"),
];
const subsReady = _.all(subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: subsReady,
people: People.find({}).fetch(),
};
},
render(){
if(this.data.subsReady == false){
return (<Loading/>);
} else {
console.log(this.data);
........
}
The same information is shown twice. Is this due to fast render that FlowRouter uses, or is it something that I am doing incorrectly?
Hmm, I guess the problem is that you are triggering the subscription every time, when the component re-renders.. I haven't tried it, but you could check if this will solve the problem
getMeteorData() {
const subsReady = _.all(this.subs || [{}], function (handle) {
if (typeof handle.ready == 'function')
return handle.ready();
return false;
});
if (!subsReady) // you can extend it, to provide params to subscriptions
this.subs = [
Meteor.subscribe("allPeople")
];
return {
subsReady: subsReady,
people: People.find({}).fetch(),
}
}
It should not trigger the subs if they are already ready.
Be aware, that mustn't pass an empty array to _.all, because of this:
_.all([], function(a) {return a.b()}) // true
this is why I added an empty object to the array, so this way you can check for the ready member..
I would suggest doing to subscription within the componentWillMount() function. This way, you make sure that you only subscribe once before the initial render().
getMeteorData() {
var ready = _.all(this.subHandles, function (handle) {
return handle.ready();
});
return {
subsReady: ready,
people: People.find({}).fetch()
}
},
componentWillMount() {
this.subHandles = [];
this.subHandles.push(Meteor.subscribe('allPeople');
},
componentWillUnmount() {
this.subHandles.map(function(handle) {
handle.stop();
});
}
If it still renders twice, I would suggest trying to turn of fast render for the route and check if this problem still occurs.

Categories