Angular 4+ Function reloading each time the component loads - javascript

So here is the function
greenToAmber() {
let x = 0;
setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
I've set this component up using the routes as you would expect, I've tried calling the function in OnInit as well, but every time I go this component then off it and back again the counter will launch a second instance of the counter & then a third ect for each time I leave & come back to the page.
From my understanding I thought ngOnDestroy was meant to prevent this, I'm assuming that I'll need to subscribe and then unsubscribe to the function maybe on destroy?
But I'm relatively new to angular 4 so pretty inexperienced.

setInterval is not destroyed on component destroy, you have to save the interval id in your class and use clearInterval javascript native function to clean it on your component destroy hook ngOnDestroy:
import {Component, OnDestroy} from '#angular/core';
#Component({ ... })
export class YourClass implements OnDestroy {
public intervalId: any;
public greenToAmber() {
let x = 0;
// registering interval
this.intervalId = setInterval(function () {
// ..... Rest of code
}, 500);
}
}
public ngOnDestroy () {
if (this.intervalId !== undefined) {
clearInterval(this.intervalId); // cleaning interval
}
}
}
Hopes it helps.

You're setting a demon process with setInterval. The behavior you mention is expected. That is how JavaScript works. Its not Angular specific.
SetInterval always returns a ID which you might want to track in your controller. When you want to destroy it, make sure you do it specifically.
eg:
greenToAmber() {
let x = 0;
$scope.myDemon = setInterval(function () {
x++;
..... Rest of code
}, 500);
}
}
//Somewhere else; where you want to destroy the interval/stop the interval:
If($scope.myDemon) {
clearInterval($scope.myDemon);
}

Related

How to make setInterval function triggered once while componentDidUpdate is fired 2 times

I'm working on a React app, and I want to manage the user inactivity.
For it, I defined a countdown which is supposed to be reset to his original value if the user is doing something in the App.
The displayed components are rendered/surrounded by a Layout component.
My problem is that the layout is updated twice after every user action, since the .setState function is used in it. Therefore, the InactivityManager Component also is updated twice and the setInterval is executed twice at the same time.
I wrote a simple InactivityManager Component, which isn't rendering anything but is rendered in the Layout.
Here is the component:
import { Component } from 'react';
import { isLogged, logout } from '...';
class InactivityManager extends Component {
constructor(props) {
super(props);
this.refreshRemainingTime = this.refreshRemainingTime.bind(this);
}
componentDidUpdate() {
if (isLogged()) {
clearInterval(this.refreshRemainingTime);
localStorage.setItem('activityCountdown', '900');
window.setInterval(this.refreshRemainingTime, 5000);
}
}
refreshRemainingTime = () => {
let activityCountdown = parseInt(localStorage.getItem('activityCountdown'), 10);
activityCountdown -= 60;
localStorage.setItem('activityCountdown', activityCountdown);
if (activityCountdown <= 0) {
logout();
localStorage.removeItem('activityCountdown');
}
};
render() {
return null;
}
}
export default InactivityManager;
Any idea of what is the best approach? Also tried to include the Component in the App.js but the Component expect only one "Route" child.
Thanks!
Can you modify the componentDidUpdate method a little bit.
componentDidUpdate() {
if (isLogged()) {
if (this.interval) clearInterval(this.interval);
localStorage.setItem('activityCountdown', '900');
this.interval = setInterval(this.refreshRemainingTime, 5000);
}
}
I finally prefered to use setTimeout instead of setInterval, there is no problem anymore with this.
Thanks!

Execute function after DOM has finished rendering

I recall reading the excerpt below from a blog.
$timeout adds a new event to the browser event queue (the rendering engine is already in this queue) so it will complete the execution before the new timeout event.
I'm wondering if there is a better way in angular/ javascript than using
setTimeout(() => {
// do something after dom finishes rendering
}, 0);
to execute code when the DOM has completely finished a task such as updating an *ngFor and rendering the results on the page.
You might try the ngAfterViewInit life-cycle hook, which is the chronologically last single-fire life-cycle hook.
https://angular.io/guide/lifecycle-hooks
It works much like ngInit but it fires after the view and child views have initialized.
If you need something that fires every time the DOM finishes you can try ngAfterViewChecked or ngAfterContentChecked.
problem:
I need to run a function sometimes after some parts loaded. (I wanted to stretch out an input and a label)
ngAfterViewInit and route change detection didn't solve my problem
Solution:
I made a component which
import { Component, AfterViewInit } from '#angular/core';
declare var jquery: any;
declare var $: any;
#Component({
selector: 'app-inline-label',
templateUrl: './inline-label.component.html',
styleUrls: ['./inline-label.component.scss']
})
/** InlineLabel component*/
/**
this component stretch inline labels and its input size
*/
export class InlineLabelComponent implements AfterViewInit {
/** InlineLabel ctor */
constructor() {
}
ngAfterViewInit(): void {
var lblWidth = $('.label-inline').width();
var parentWidth = $('.label-inline').parent().width();
var fieldWidth = parentWidth - lblWidth;
$('.form-control-inline').css("width", fieldWidth);
}
}
then I used it anywhere in my html like
<app-inline-label></app-inline-label>
even if my html had *ngIf="", I used app-inline-label inside that tag and solved all my problems
Actually it will be fired exactly when <app-inline-label> </app-inline-label> being rendered
If the function to be rendered multiple times ngAfterContentChecked will be preferable.
app.component.ts
export class AppComponent implements OnInit, OnDestroy {
searchRegister: any = [];
constructor() {
}
ngAfterContentChecked(): void {
this.setHTMLElements();
}
setHTMLElements() {
this.searchRegister = ['cards-descriptor__subtitle','insights-card__title','article-headline__title','wysiwyg__content','footer-logo__heading','hero-breadcrumbs__blurb','multi-column-text__body','small-two-column-image-text__blurb','two-column-image-text__blurb','image-blurbs-expandable__desc',];
for (var val of this.searchRegister) {
var classLength = this.dom.body.getElementsByClassName(val).length;
for (var i = 0; i <= classLength; i++) {
if (
this.dom.body.getElementsByClassName(val)[i]?.innerHTML != undefined
) {
this.dom.body.getElementsByClassName(val)[
i
].innerHTML = this.dom.body
.getElementsByClassName(val)
[i]?.innerHTML?.replace(/[®]/gi, '<sup>®</sup>');
}
}
}
}
}
Other.component.ts
import { AppComponent } from '../../app.component';
export class IndustryComponent implements OnInit {
constructor(private appComponent: AppComponent) { }
ngAfterContentChecked(): void {
this.appComponent.setHTMLElements();
}
}

Calling action inside function inside componentWillMount causing Cannot read property of undefined

Pretty new to React and ES6 conventions. I am trying to call an action from within a function that is inside of a componentWillMount(). This is resulting in an Uncaught TypeError: Cannot read property 'signoutUser' of undefined. Not quite sure how to resolve it, tried binding this, which did resolve the problem.
This my code in its current form:
// left_site.js
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { signoutUser } from '../actions/authentication';
export default function(ComposedComponent) {
class LeftSite extends Component {
constructor(props, context) {
super(props, context);
}
componentWillMount() {
var timerLeft = setInterval(timer, 1000);
function timer() {
if ((sessionStorage.getItem('timer') > 0) &&
(new Date().getTime() - sessionStorage.getItem('timer') > 5000)) {
this.props.signoutUser();
} else {
sessionStorage.setItem('timer', new Date().getTime());
}
}
}
render() {
return <ComposedComponent {...this.props} />
}
}
return connect( null, { signoutUser })(LeftSite);
}
To explain what is going on, the company wants the user to automatically be logged out if they navigate away from any of the protected routes on the domain to another domain. One idea was to create a "heartbeat" that committed the time to sessionStorage every second so long as the user is on a protected route on the domain. The idea is if the navigate to another domain, then try to come back, if the difference in the last stored time and the current time is greater than 5000ms, it will automatically signout.
There may be a better way to do this, but I couldn't think of one that 1) didn't violate privacy or 2) wouldn't trigger the logout with a refresh, unbind for example.
The left_site.js is a HOC--I also have a required_login.js HOC to reroute to the login page if someone tries to access the protected route without authentication--so my protected routes are wrapped in component={LeftSite(RequireAuth(Home))}.
LeftSite is running fine, however when the conditional evaluates to true and it tries to trigger the this.props.signoutUser(); the error comes up.
Function timer is not bound to class. When it is executed at regular interval, the execution context changes. You have to bind the function before use. Also make sure you clear the interval at proper time, or when the component unmounts. I suggest you write this way
timer = () => {
if ((sessionStorage.getItem('timer') > 0) &&
(new Date().getTime() - sessionStorage.getItem('timer') > 5000)) {
this.props.signoutUser();
} else {
sessionStorage.setItem('timer', new Date().getTime());
}
}
componentWillMount() {
this.timerLeft = setInterval(this.timer, 1000)
}
componentWillUnmount() {
clearInterval(this.timerLeft);
}
You need to bind this to the timer function. The easier and recommended way to do this is by defining an arrow function for your timer, like this:
export default class MyComponent extends React.Component {
componentWillMount() {
const timer = () => {
console.log(this.props); // should be defined
};
const timerLeft = setInterval(timer, 1000);
}
}
This works because with arrow functions, this is set to the this of the enclosing context.

Flux/Alt setTimeout not updating store

I'm trying to create a basic "Toast" like service in my React app using Alt.
I've got most of the logic working, I can add new items to the array which appear on my view when triggering the add(options) action, however I'm trying to also allow a timeout to be sent and remove a toast item after it's up:
onAdd(options) {
this.toasts.push(options);
const key = this.toasts.length - 1;
if (options.timeout) {
options.timeout = window.setTimeout(() => {
this.toasts.splice(key, 1);
}, options.timeout);
}
}
On add, the toast appears on my page, and the timeout also gets triggered (say after a couple of seconds), however manipulating this.toasts inside of this setTimeout does not seem to have any effect.
Obviously this is missing the core functionality, but everything works apart from the setTimeout section.
It seems that the timeout is setting the state internally and is not broadcasting a change event. It might be as simple as calling forceUpdate(). But the pattern I use is to call setState() which is what I think you might want in this case.
Here is an example updating state and broadcasting the change event.
import alt from '../alt'
import React from 'react/addons'
import ToastActions from '../actions/ToastActions'
class ToastStore {
constructor() {
this.toasts = [];
this.bindAction(ToastActions.ADD, this.add);
this.bindAction(ToastActions.REMOVE, this.remove);
}
add(options) {
this.toasts.push(options);
this.setState({toasts: this.toasts});
if (options.timeout) {
// queue the removal of this options
ToastActions.remove.defer(options);
}
}
remove(options) {
const removeOptions = () => {
const toasts = this.toasts.filter(t => t !== options);
this.setState({toasts: toasts});
};
if (options.timeout) {
setTimeout(removeOptions, options.timeout);
} else {
removeOptions();
}
}
}
module.exports = alt.createStore(ToastStore, 'ToastStore');

this.myService.myEvent.toRx().subscribe() called but no DOM refresh (Zone trigger)

I'm playing with angular2 alpha 40 with ng2-play starter from pawel.
Examples are in typescript.
I have a service MovieList like this:
export class Movie {
selected: boolean = false
constructor(public name:string, public year:number, public score:number) {}
}
export class MovieListService {
list: Array<Movie>
selectMovie = new EventEmitter();
constructor() {
this.list = [new Movie('Star Wars', 1977, 4.4)];
}
add(m:Movie) {
this.list.push(m);
}
remove(m:Movie) {
for(var i = this.list.length - 1; i >= 0; i--) {
if(this.list[i] === m) {
if(m.selected) this.selectMovie.next();
this.list.splice(i, 1);
}
}
}
select(m:Movie) {
this.list.map((m) => m.selected = false);
m.selected = true;
this.selectMovie.next(m);
}
}
I have a component showing the movies list and make possible to select one by clicking on it, which call select() in the service above.
And I have another component (on the same level, I don't want to use (selectmovie)="select($event)") which subscribe to the movie selection event like this:
#Component({
selector: 'movie-edit',
})
#View({
directives: [NgIf],
template: `
<div class="bloc">
<p *ng-if="currentMovie == null">No movie selected</p>
<p *ng-if="currentMovie != null">Movie edition in progress !</p>
</div>
`
})
export class MovieEditComponent {
currentMovie:Movie
constructor(public movieList: MovieListService) {
this.movieList.selectMovie.toRx().subscribe(this.movieChanged);
setTimeout(() => { this.movieChanged('foo'); }, 4000);
}
movieChanged(f:Movie = null) {
this.currentMovie = f;
console.log(this.currentMovie);
}
}
The event is subscribed using .toRx().subscribe() on the eventEmitter.
movieChanged() is called but nothing happen in the template..
I tried using a timeout() calling the same function and changes are refleted in the template.
The problem seems to be the fact that subscribe expects an Observer or three functions that work as an observer while you are passing a normal function. So in your code I just changed movieChanged to be an Observer instead of a callback function.
movieChanged: Observer = Observer.create(
(f) => { this.currentMovie = f; }, // onNext
(err) => {}, // onError
() => {} // onCompleted
);
See this plnkr for an example. It would have been nice to see a minimal working example of your requirement so my solution would be closer to what you are looking for. But if I understood correctly this should work for you. Instead of a select I just used a button to trigger the change.
Update
You can avoid creating the Òbserver just by passing a function to the subscriber method (clearly there's a difference between passing directly a function and using a class method, don't know really why is different)
this.movieList.selectMovie.toRx().subscribe((m: Movie = null) => {
this.currentMovie = m;
});
Note
EventEmitter is being refactored, so in future releases next will be renamed to emit.
Note 2
Angular2 moved to #reactivex/rxjs but in the plnkr I'm not able to use directly those libs (didn't find any cdn). But you can try in your own project using these libs.
I hope it helps.
The movieChanged function expects the movie object and not the String. Try changing below code
setTimeout(() => { this.movieChanged('foo'); }, 4000);
to
setTimeout(() => { this.movieChanged(new Movie('Troy', 2000 , 8)); }, 4000);

Categories