'angular, quirky memory leak

Using Angular ~11. I've got memory leak that keeps the component in memory. I've hollowed out the html and the component.ts. I can get the component to release if I don't have anything inside a particular div, but soon as I put simple text in (no binding, just text) i get a memory leak. the component sticks in memory.

here is html. Note everything is commented out. I have the hello world div, that works if i comment out the Uh-Oh memory tag.

uh-memory leak text in, memory leak. uh-memory leak text commented out, not memory leak.

What is going on here?

<h1 style="background-color:aquamarine" (click)="clickMain($event)">Hello World</h1>
<div>
  <h1>I'm a potato</h1>
</div>
<div class="pour-page no-selection" (click)="clickMain($event)">
  <!-- <div class="water-selector no-selection" [class.hide]="isPouringStarted" (click)="onSelectorClicked($event)">
    <v-water-selector vid="pour_water_selector"></v-water-selector>
  </div> -->
  <h2>Uh-Oh, memory leak</h2>
  <!-- drink configuration -->
  <!-- <mat-card class="details-card no-selection" [class.hide]="isPouringStarted" (click)="onCardClicked($event)">
    <div class="top-container no-selection">
      <div class="thumbnail-container no-selection">
        <img class="thumbnail no-selection" [src]="logo" />
      </div>
      <div class="flavor-name-container no-selection" [style.color]="textColor">
        <p class="flavor-name no-selection" [class.water]="isWater">{{ flavorName }}</p>
      </div>
      <p class="calories-container no-selection">10 CAL PER 40 FL OZ</p>
      <app-flavor-intensity *ngIf="isNotWater"></app-flavor-intensity>
      <app-sparkling-intensity *ngIf="isSparkling"></app-sparkling-intensity>
      <app-water-temperature [isSparkling]="isSparkling"></app-water-temperature>
    </div>
    <div class="divider"></div>
    <app-auto-fill></app-auto-fill>
  </mat-card> -->

  <!-- <div [ngClass]="isPouringStarted ? 'pour-logo' : 'non-pour-logo'">
    <div class="thumbnail-container">
      <img
        class="thumbnail no-selection"
        [src]="logo"
        [@resizeAnimation]="{
          value: isPouringStarted ? 'grow' : 'shrink',
          params: { maxHeight: '605px', minHeight: '300px', growTime: '0.5s', shrinkTime: '0.5s' }
        }"
      />
    </div>
  </div> -->

  <!-- <div class="footer-container no-selection" (click)="onFooterClicked($event)">
    <div class="pour-btn-container no-selection">
      <v-pour-button [isPouring]="isPouring" vid="pour_btn" style="user-select: none !important"></v-pour-button>
    </div>
  </div> -->
  <!-- <div class="done-btn-container" *ngIf="isAutoPour && !isPouring">
    <v-button vid="done_btn"></v-button>
  </div> -->

  <!-- <div class="absolute-container download-container" *ngIf="isPouringStarted">
    <div class="pouring-circle">
      <div class="phone-img">
        <app-phone-circle-svg [color1]="gradientColor1" [color2]="gradientColor2"></app-phone-circle-svg>
      </div>
      <div class="title">
        <v-text-block vid="download_app"></v-text-block>
      </div>
      <div class="hydration-container">
        <v-text-block vid="track_hydration"></v-text-block>
      </div>
    </div>
  </div> -->

  <!-- <div class="absolute-container bottle-saved-container" *ngIf="isPouringStarted">
    <div class="pouring-circle no-selection" [style.color]="textColor">
      <div class="bottle-img">
        <app-bottle-svg [color1]="gradientColor1" [color2]="gradientColor2"></app-bottle-svg>
      </div>
      {{ bottlesSaved }}
      <div class="title">
        <v-text-block vid="bottle_saved"></v-text-block>
      </div>
      <div class="hydration-container">
        {{ bottleSavedText }}
      </div>
    </div>
  </div> -->
</div>

now, the component typescript, again, most of it is commented out, what's left is what t needs to navigate away. Not the clickMain, works fine if uh-oh is commented out.

import { Component, OnDestroy, OnInit } from '@angular/core';
import { ConfigurationData, LineAssignment, PubSubTopic } from 'src/app/universal/app.types';
import { NavigationStart, Router } from '@angular/router';

import { AppInfoService } from 'src/app/services/app-info.service';
import { JsUtil } from 'src/app/pubsub/JsUtil';
import { LocalizationService } from 'src/app/services/localization.service';
import { PourService } from 'src/app/services/pour.service';
import { SelectionController } from 'src/app/services/selection-controller';
import { SubscribeEvent } from 'src/app/pubsub/pubsub.builder';
import { Subscription } from 'rxjs';
import { resizeAnimation } from 'src/app/ui/animations/animations';
import { SettingsManager } from 'src/app/services/settings-manager.service';

@Component({
  selector: 'app-pour-page',
  templateUrl: './pour-page.component.html',
  styleUrls: ['./pour-page.component.scss'],
  animations: [resizeAnimation]
})
export class PourPageComponent implements OnInit, OnDestroy {
  objectId = JsUtil.getObjectId();
  ignoreMainClick = true;
  isPouringStarted = false;
  _wasPouring: boolean = false;
  timeoutTimer: NodeJS.Timer;
  ignoreInitialClickTimer: NodeJS.Timer;
  timeoutDelay = 5;

  private routeSub: Subscription;

  constructor(
    private appInfo: AppInfoService,
    private pourService: PourService,
    private router: Router,
    private selectionController: SelectionController,
    private localizationService: LocalizationService
  ) {
    console.log('ctor.PourPageComponent');
    // if (!this.appInfo.selectedFlavor) {
    //   this.appInfo.navigateToPage('v1/selector');
    // }
    // this.routeSub = this.router.events.subscribe((event) => {
    //   if (event instanceof NavigationStart) {
    //     // this.pourService.stopPour();
    //     // this.selectionController.resetDefault();
    //     // this.pourService.resetAutoPour();
    //   }
    // });

    // SubscribeEvent.Create(PubSubTopic.configurationDataReady, this.objectId)
    //   .HandleEventWithThisMethod((e) => this.onDataReady(e.data))
    //   .Done();
  }

  // private onDataReady(e: ConfigurationData) {
  //   this.timeoutDelay = SettingsManager.getAsNumber('pourpage.timeout.delay');
  // }

  // get selectedFlavor(): LineAssignment {
  //   return this.appInfo.selectedFlavor;
  // }

  // get isSparkling(): boolean {
  //   return this.appInfo.selectedWater === 'sparkling';
  // }

  // get gradientColor1(): string {
  //   return this.selectedFlavor.bibItem.design.gradientColor1;
  // }

  // get isAutoPour(): boolean {
  //   return this.pourService.isAutoPour;
  // }

  // get gradientColor2(): string {
  //   return this.selectedFlavor.bibItem.design.gradientColor2;
  // }

  // get isPouring(): boolean {
  //   this.setIsPouringStarted();
  //   return this.pourService.isPouring;
  // }

  // private setIsPouringStarted() {
  //   if (this.pourService.isPouring) {
  //     this.isPouringStarted = true;
  //   }

  //   if (this.pourService.isPouring !== this._wasPouring && this.timeoutTimer) {
  //     clearTimeout(this.timeoutTimer);
  //   }

  //   if (!this.pourService.isPouring && this._wasPouring) {
  //     this.timeoutTimer = setTimeout(() => {
  //       this.appInfo.navigateToPage('v1/selector');
  //     }, this.timeoutDelay * 1000);
  //   }

  //   this._wasPouring = this.pourService.isPouring;
  // }

  // get isNotWater(): boolean {
  //   // return this.selectedFlavor.bibItem.Id !== 'carb-water' && this.appInfo.selectedFlavor.bibItem.Id !== 'still-water';
  //   //return this.selectedFlavor.bibItem.Type !== 'water';
  // }

  // get bottlesSaved(): number {
  //   return this.appInfo.bottlesSaved;
  // }

  // get bottleSavedText(): string {
  //   return `${this.bottlesSaved} 16-oz ${this.localizationService.localizeString('plastic_bottles_id')}`;
  // }

  // get logo(): string {
  //   if (this.appInfo.selectedWater === 'still') {
  //     return this.selectedFlavor.bibItem.design.stillImageUrl;
  //   }

  //   return this.selectedFlavor.bibItem.design.sparklingImageUrl;
  // }

  // get flavorName(): string {
  //   return this.selectedFlavor.bibItem.design.title;
  // }

  // get textColor(): string {
  //   return this.selectedFlavor.bibItem.design.textColor;
  // }

  // get isWater(): boolean {
  //   return this.selectedFlavor.bibItemSKU === 'plain' || this.selectedFlavor.bibItemSKU === 'carb';
  // }

  ngOnInit(): void {
    this.ignoreInitialClickTimer = setTimeout(() => {
      this.ignoreMainClick = false;
    }, 100);
  }

  ngOnDestroy(): void {
    //this.routeSub.unsubscribe();

    SubscribeEvent.UnSubscribeByConsumer(this.objectId);

    if (this.timeoutTimer) {
      clearTimeout(this.timeoutTimer);
    }

    clearTimeout(this.ignoreInitialClickTimer);
  }

  clickMain(event: MouseEvent) {
    event.stopPropagation();

    if (this.ignoreMainClick === false) {
      this.appInfo.navigateToPage('v1/selector');
    }
  }

  // onSelectorClicked(event: MouseEvent) {
  //   event.stopPropagation();
  // }

  // onCardClicked(event: MouseEvent) {
  //   event.stopPropagation();
  // }

  // onFooterClicked(event: MouseEvent) {
  //   event.stopPropagation();
  // }

  // get flavorColor(): string {
  //   return this.appInfo.selectedFlavor.bibItem.design.color;
  // }
}



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source