Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.

Commit fae1583

Browse files
committed
Updated the autosave directive to use observables for timing instead of the timeout service
rxjs has a better way of handling cancellations, since we couldn't get an actual timer cancel to work in the timeout service. Also, this is probably a better way to go anyway since rxjs handles it. To handle this, we introduce two streams, one for autosave starts and one for autosave cancels. When the form becomes dirty (on a status change) we initiate a stream that debounces off the autosave start events, takes until an autosave cancel event, and then fires and autosave. The keyup events then just push autosave start events to the stream. Also, added a final dirty and validation check before the actual save is fired. This addresses an issue where an autosaving card could save twice if the card close triggers a save, but the autosave never resets, and thus never checks to see that the form became pristine.
1 parent a8b6fc7 commit fae1583

File tree

2 files changed

+32
-21
lines changed

2 files changed

+32
-21
lines changed

source/behaviors/autosave/autosave.tests.ts

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,6 @@
11
import { Subject } from 'rxjs';
22
import { rlFakeAsync, mock, rlTick, flushMicrotasks } from 'rl-async-testing';
33

4-
import { services } from 'typescript-angular-utilities';
5-
64
import { AutosaveDirective, DEFAULT_AUTOSAVE_DEBOUNCE } from './autosave';
75

86
interface IFormMock {
@@ -35,7 +33,7 @@ describe('AutosaveDirective', () => {
3533

3634
autosaveAction = { trigger: sinon.spy() };
3735

38-
autosave = new AutosaveDirective(<any>form, new services.timeout.TimeoutService(), <any>autosaveAction);
36+
autosave = new AutosaveDirective(<any>form, <any>autosaveAction);
3937
});
4038

4139
describe('ngAfterViewInit', (): void => {
@@ -178,5 +176,15 @@ describe('AutosaveDirective', () => {
178176
sinon.assert.calledOnce(autosaveAction.trigger);
179177
sinon.assert.calledWith(autosaveAction.trigger, waitValue);
180178
});
179+
180+
it('should not save if the form becomes pristine immediately before saving', () => {
181+
const waitValue = mock.request()();
182+
form.submitAndWait = sinon.spy(() => waitValue);
183+
form.dirty = false;
184+
185+
autosave.autosave();
186+
187+
sinon.assert.notCalled(autosaveAction.trigger);
188+
});
181189
});
182190
});

source/behaviors/autosave/autosave.ts

Lines changed: 21 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
11
import { Directive, Input, Self, AfterViewInit, HostListener } from '@angular/core';
2-
3-
import { services } from 'typescript-angular-utilities';
4-
import __timeout = services.timeout;
2+
import { Observable, Subject } from 'rxjs';
53

64
import { FormComponent } from '../../components/form/form';
75
import { AutosaveActionService } from '../../services/autosaveAction/autosaveAction.service';
@@ -16,16 +14,15 @@ export class AutosaveDirective implements AfterViewInit {
1614
@Input() saveWhenInvalid: boolean;
1715
@HostListener('keyup') keyupListener = this.resetDebounce;
1816

19-
timer: __timeout.ITimeout;
2017
form: FormComponent;
21-
timeoutService: __timeout.TimeoutService;
2218
autosaveAction: AutosaveActionService;
2319

20+
autosaveStart$: Subject<void> = new Subject<void>();
21+
autosaveCancel$: Subject<void> = new Subject<void>();
22+
2423
constructor( @Self() form: FormComponent
25-
, timeoutService: __timeout.TimeoutService
2624
, autosaveAction: AutosaveActionService) {
2725
this.form = form;
28-
this.timeoutService = timeoutService;
2926
this.autosaveAction = autosaveAction;
3027
}
3128

@@ -34,32 +31,34 @@ export class AutosaveDirective implements AfterViewInit {
3431
}
3532

3633
ngOnDestroy(): void {
37-
if (this.timer) {
38-
this.timer.cancel();
39-
}
34+
this.autosaveCancel$.next();
4035
}
4136

4237
setDebounce = (): void => {
43-
if (!this.timer && this.form.dirty && (this.saveWhenInvalid || this.form.validate())) {
44-
this.timer = this.timeoutService.setTimeout(this.autosave, DEFAULT_AUTOSAVE_DEBOUNCE)
45-
.catch(() => null);
38+
if (this.canAutosave()) {
39+
this.autosaveCancel$.next();
40+
this.autosaveStart$.debounceTime(DEFAULT_AUTOSAVE_DEBOUNCE).takeUntil(this.autosaveCancel$).subscribe(() => this.autosave());
41+
this.autosaveStart$.next();
42+
} else {
43+
this.autosaveCancel$.next();
4644
}
4745
}
4846

4947
resetDebounce(): void {
50-
if (this.timer) {
51-
this.timer.cancel();
52-
this.timer = null;
53-
this.setDebounce();
48+
if (this.canAutosave()) {
49+
this.autosaveStart$.next();
5450
}
5551
}
5652

5753
autosave = (): void => {
54+
if (!this.canAutosave()) {
55+
return;
56+
}
57+
5858
const waitOn = this.submitAndWait();
5959
if (waitOn) {
6060
this.autosaveAction.trigger(waitOn);
6161
}
62-
this.timer = null;
6362
}
6463

6564
submitAndWait(): IWaitValue<any> {
@@ -69,4 +68,8 @@ export class AutosaveDirective implements AfterViewInit {
6968
return this.form.submitAndWait();
7069
}
7170
}
71+
72+
private canAutosave(): boolean {
73+
return this.form.dirty && (this.saveWhenInvalid || this.form.validate());
74+
}
7275
}

0 commit comments

Comments
 (0)