I have an Angular (not js) app which displays a grid of images. This grid is massive and so automatically scrolls the users browser window and at timed intervals, randomly selects and displays one of the images (using lightbox). This all works fine for the most part.
However, during certain interactions such as when the user manually de-selects the displayed image, or when the user navigates away from the "view" page, it becomes obvious that the TimerObservable's that run the auto-scrolling and the auto-selecting are not destroyed.
This causes 2 unwanted side-effects; firstly, the browser window continues to scroll up/down even when on other pages that are not appropriate to scroll (these other components don't even have auto-scroll code in them!!). Secondly, the scroll becomes twice the speed, as if two TimerObservable's have been created and are both scrolling the document window.
Here is my component code (with irrelevant stuff removed):
You can see here that i create 2 x Subscription variables to hold the subscriptions created within the StartAutoScroll() and StartAutoSelector() functions. I then use the StopUI() and StartUI() functions to stop/unsubscribe/destroy these subscriptions but as I state earlier, this does not always occur, the nudge()& focusTile() functions are still being called even after I navigate away from this component (sometimes) or manually de-select the lightbox myself.
import { Component, OnInit, ViewEncapsulation} from '@angular/core';
import { Router, ActivatedRoute, ParamMap } from '@angular/router';
import { switchMap } from 'rxjs/operators';
import { TimerObservable } from 'rxjs/observable/TimerObservable';
import { TileViewService } from '../services/tile-view.service';
import { TileView } from '../models/TileView';
import { Lightbox, LIGHTBOX_EVENT, LightboxEvent } from 'ngx-lightbox';
import { WindowRefService } from '../services/window-ref.service';
import { Subscription } from 'rxjs';
@Component({
selector: 'app-tile-view',
templateUrl: './tile-view.component.html',
styleUrls: ['./tile-view.component.scss'],
encapsulation: ViewEncapsulation.None
})
export class TileViewComponent implements OnInit {
// These are the subscriptions causing issues
public isAutoScrolling : Boolean = true;
private autoScroller: Subscription;
public isAutoSelecting : Boolean = true;
private autoSelector: Subscription;
tileView: TileView;
private tileAlbum: Array<any> = [];
public isFrozen : Boolean;
private nudgeValue = 1;
public focusTileDelay = 8000;
public focusTileDuration = 5000;
private lightboxSubscription : Subscription;
private albumIndicies: Array<number> = [];
constructor(
private route: ActivatedRoute,
private router: Router,
private tileService: TileViewService,
private windowRef: WindowRefService,
private lightbox: Lightbox,
private lightboxEvent: LightboxEvent
) { }
ngOnDestroy() {
console.info("tile-view destroyed!");
this.stopUI();
}
ngOnInit() {
var result = this.route.paramMap.pipe(
switchMap((params: ParamMap) =>
this.tileService.getTileById(params.get('id'))
)
).subscribe((response) => {
this.startUI();
});
}
public stopUI(){
this.stopAutoScroll();
this.stopTileSelector();
}
public startUI(){
if(this.isAutoScrolling){
this.startAutoScroll();
}
else{
this.stopAutoScroll();
}
if(this.isAutoSelecting){
this.startTileSelector();
}
else{
this.stopTileSelector();
}
}
private startAutoScroll() {
this.autoScroller = TimerObservable.create(0, 75).subscribe(x => {
this.nudge();
});
console.debug("STARTING document scroll", this.autoScroller);
}
private stopAutoScroll(){
this.autoScroller.unsubscribe();
console.debug("STOPPING document scroll", this.autoScroller);
}
private startTileSelector() {
this.autoSelector = TimerObservable.create(this.focusTileDelay, this.focusTileDuration).subscribe(x => {
this.focusNextTile();
});
console.debug("STARTING tile selection", this.autoSelector);
}
private stopTileSelector(){
this.autoSelector.unsubscribe();
console.info("STOPPING tile selection", this.autoSelector);
}
/**
* Destroys any existing instances of a lightbox display and shows
* a new lightbox element for the specific album item.
* @param index The item-position in the album to display
*/
private focusTile(index: number): void {
console.info("Focusing on tile #" + index);
// Stop scrolling / selecting while we focus this tile
this.stopUI();
// Subscribe to events that this lightbox may emit
this.lightboxSubscription = this.lightboxEvent.lightboxEvent$.subscribe(event => this.OnLightboxReceivedEvent(event));
// Actually display lightbox for selected focus
this.lightbox.open(this.tileAlbum, index, {centerVertically: true});
// If the tile isnt frozen, automatically de-select it after the standard viewing-duration
if(!this.isFrozen){
setTimeout(() =>{
this.unfocusCurrentTile();
},
this.focusTileDuration);
}
}
/**
* Removes any existing lightbox instances, re-starts UI
*/
private unfocusCurrentTile(){
this.stopUI();
var overlays = document.getElementsByClassName('lightboxOverlay');
var lightboxes = document.getElementsByClassName('lightbox');
for(var i = 0; i < overlays.length; i++){
overlays[i].remove();
}
for(var i = 0; i < lightboxes.length; i++){
lightboxes[i].remove();
}
this.startUI();
}
private nudge() {
if ((this.windowRef.nativeWindow.innerHeight + this.windowRef.nativeWindow.scrollY) >= document.body.offsetHeight) {
this.nudgeValue = -1;
}
else if (this.windowRef.nativeWindow.scrollY == 0) {
this.nudgeValue = 1;
}
this.windowRef.nativeWindow.scrollTo(
0,
this.windowRef.nativeWindow.scrollY + this.nudgeValue
);
}
}