I don’t have a specific answer, but here are a few concepts you may end up using in your solution.
First, you can do change detection on a specific form field:
view.component.ts
:
@inject(CanDeactiveFormService)
export class SomeViewComponent {
@bindable({ defaultBindingMode: bindingMode.twoWay })
firstName: string;
public constructor(private canService: CanDeactiveFormService) {}
firstNameChanged(newValue, oldValue) {
this.canService.isModified = true;
}
}
Then your canDeactivate functionality can check the service.
Of course, if you have your form fields in an object, that won’t work since Aurelia v1 doesn’t track deep changes. Instead, you’ll need to use a custom deep watch on your object.
multi-properties-binding-engine.js
import { inject, ObserverLocator, TaskQueue } from 'aurelia-framework';
@inject(ObserverLocator, TaskQueue)
export class MultiPropertiesBindingEngine {
constructor(private observerLocator: ObserverLocator,
private taskQueue: TaskQueue) {
}
/**
* @param {object} obj
* @param {string[]} props
*/
propertiesObserver(obj: {}, props: string[]) {
return {
subscribe: (callback: Function) => {
let isCallbackQueued = false;
const unmarkQueued = () => {
isCallbackQueued = false;
};
const handler = () => {
if (!isCallbackQueued) {
this.taskQueue.queueMicroTask(unmarkQueued);
this.taskQueue.queueMicroTask(callback);
isCallbackQueued = true;
}
};
const observers = props.map(prop => this.observerLocator.getObserver(obj, prop));
observers.forEach(observer => {
observer.subscribe(handler);
});
return {
observers: observers,
dispose: () => {
observers.forEach(observer => {
observer.unsubscribe(handler);
});
}
};
}
}
}
}
import { MultiPropertiesBindingEngine } from 'multi-properties-binding-engine';
@inject(Router, CanDeactiveFormService, MultiPropertiesBindingEngine)
export class AdminUserEditView {
formData: SomeModel;
private subscriptions: Disposable[] = [];
constructor(
private router: Router,
private canService: CanDeactiveFormService,
private bindingEngine: MultiPropertiesBindingEngine) {
}
async activate(params: any, routeConfig: RouteConfig): Promise<any> {
this.subscriptions.push(this.bindingEngine
.propertiesObserver(this.formData, ['status', 'text', 'category', 'dateFrom', 'dateTo'])
.subscribe(this.formDataChanged.bind(this)));
}
formDataChanged(newValue, oldValue) {
this.canService.isModified = true;
}
detached() {
if (this.subscriptions && this.subscriptions.length) {
this.subscriptions.forEach((disposable) => disposable.dispose());
while (this.subscriptions.length) {
this.subscriptions.pop();
}
}
}
}
I haven’t looked into any type of reactive forms with Aurelia, but you should be able to subscribe to a form-level change event. This is an interesting case, and I’d be interested in learning how you solve it. There must be an elegant solution instead of such a hack above.