Mark
Sweep
marked=false
marked=false
marked=true
marked=true
marked=true
marked=true
@Component({
selector: 'app-root',
template: `<app-sub *ngIf="hide"></app-sub>`
})
export class AppComponent {
hide = false;
constructor() {
setInterval(() => this.hide = !this.hide, 50);
}
}
@Injectable()
export class DummyService {
behavior$ = new BehaviorSubject(2);
replay$ = new ReplaySubject(1);
private registeredComponents = [];
register(component) {
this.registeredComponents.push(component);
}
}
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.subject.subscribe();
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.subject.subscribe(() => {const bla = 55});
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.subject.subscribe(() => this.rand2 = 33);
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.dummyService.behavior$.subscribe();
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.dummyService.behavior$.subscribe(() => {const bla = 55});
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.dummyService.behavior$.subscribe(() => this.rand2 = 33);
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent {
rand = Math.random();
rand2 = 0;
subject = new BehaviorSubject(42);
arr = [];
constructor(private dummyService: DummyService) {
for (let i = 0; i < 100000; ++i) {
this.arr.push(Math.random());
}
this.dummyService.register(this);
}
}
@Injectable()
export class DummyService {
behavior$ = new BehaviorSubject(2);
replay$ = new ReplaySubject(1);
private registeredComponents = [];
register(component) {
this.registeredComponents.push(component);
}
}
Reminder: SubComponent is shown / hidden periodically with *ngIf
ng
Component
window
Observable
ng
Component
window
Observable
Service
Subscribers
Subscribers
@Component({
selector:'app-sub',
template: 'mega {{rand}}'
})
export class SubComponent implements ngOnDestroy {
private destroy$ = new Subject<void>();
constructor(private dummyService: DummyService) {
this.dummyService.behavior$.pipe(
takeUntil(this.destroy$)
).subscribe((this => this.rand2 = 33));
this.dummyService.behavior$.pipe(
takeUntil(this.destroy$),
shareReplay(1),
takeUntil(this.destroy$)
).subscribe((this => this.rand2 = 33));
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
must be preceded by
takeUntil(this.destroy$)
npm install @mobi/rwc-lint-rules-jslib --save-dev
Use the "rxjs-prefer-angular-takeuntil-before-subscribe" rule
from @mobi/rwc-lint-rules-jslib
{
"extends": ["@mobi/rwc-lint-rules-jslib"],
"rules": {
"rxjs-prefer-angular-takeuntil-before-subscribe": { "severity": "error" },
...
}
}
ng lint
tslint.json
export class BaseComponent implements OnDestroy {
private destroy$ = new Subject();
constructor(private dummyService: DummyService) {
this.dummyService.behavior$.pipe(takeUntil(this.destroy$))
.subscribe(() => this.doSomething());
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
export class FancyComponent extends BaseComponent implements OnDestroy {
private _destroy$ = new Subject();
constructor(private dummyService: DummyService) {
this.dummyService.behavior$.pipe(takeUntil(this._destroy$))
.subscribe(() => this.doSomethingElse());
}
ngOnDestroy() {
this._destroy$.next();
this._destroy$.complete();
}
}
export class BaseComponent implements OnDestroy {
private destroy$ = new Subject();
constructor(private dummyService: DummyService) {
this.dummyService.behavior$.pipe(takeUntil(this.destroy$))
.subscribe(() => this.doSomething());
}
ngOnDestroy() {
this.destroy$.next();
this.destroy$.complete();
}
}
export class FancyComponent extends BaseComponent implements OnDestroy {
private _destroy$ = new Subject();
constructor(private dummyService: DummyService) {
this.dummyService.behavior$.pipe(takeUntil(this._destroy$))
.subscribe(() => this.doSomethingElse());
}
ngOnDestroy() {
super.ngOnDestroy();
this._destroy$.next();
this._destroy$.complete();
}
}
var count = 0;
var intervalTime = 10*1000; // 10s
var inter = setInterval(function(){
console.log("Leak Test: Click " + (count + 1));
var menuItems = $('.app-navigation-body .menu .menu-item');
menuItems[count % menuItems.length].click();
count++;
}, intervalTime);
// Stop it:
// clearInterval(inter)
Memory Tab / Performance Monitor
With Memory Leak
Without Memory Leak
With (subtle) Memory Leak