Last active
August 25, 2023 07:38
-
-
Save pratheeshrussell/7ae0ef1ba05ee2c4a26d2705eb012151 to your computer and use it in GitHub Desktop.
An Angular Decorator to call a function on value change
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// This is a modified version of property-watch-decorator to support objects | |
// Refer: https://github.com/zhaosiyang/property-watch-decorator | |
// might work but not optimal for deeply nested structures | |
export interface ChangeDetails<T> { | |
propertyKey: PropertyKey; | |
firstChange: boolean | null; | |
previousValue: T; | |
currentValue: T | undefined; | |
isFirstChange: () => boolean | null; | |
} | |
export type CallBackFunction<T> = ( | |
value: T | undefined, | |
change?: ChangeDetails<T> | |
) => void; | |
function createObjectProxy<T = any>( | |
obj: any, | |
callBackFn: CallBackFunction<T>, | |
path: any[] = [] | |
) { | |
return new Proxy(obj, { | |
get(target, key): any { | |
if (typeof target[key] === 'object' && target[key] !== null) { | |
return createObjectProxy(target[key], callBackFn, path.concat(key)); | |
} | |
return target[key]; | |
}, | |
set(target, key, value) { | |
const oldValue = target[key]; | |
const simpleChange: ChangeDetails<T> = { | |
propertyKey: [...path, key].join('.'), | |
firstChange: null, | |
previousValue: oldValue, | |
currentValue: value, | |
isFirstChange: () => null, | |
}; | |
target[key] = value; | |
callBackFn.call(this, value, simpleChange); | |
return true; | |
}, | |
deleteProperty(target, key) { | |
const oldValue = target[key]; | |
const simpleChange: ChangeDetails<T> = { | |
propertyKey: [...path, key].join('.'), | |
firstChange: null, | |
previousValue: oldValue, | |
currentValue: undefined, | |
isFirstChange: () => null, | |
}; | |
delete target[key]; | |
callBackFn.call(this, undefined, simpleChange); | |
return true; | |
}, | |
}); | |
} | |
export function OnChange<T = any>(callback: CallBackFunction<T> | string) { | |
const cachedValueKey = Symbol(); | |
const isFirstChangeKey = Symbol(); | |
return (target: any, key: PropertyKey) => { | |
const callBackFn: CallBackFunction<T> = | |
typeof callback === 'string' ? target[callback] : callback; | |
if (!callBackFn) { | |
throw new Error( | |
`Cannot find method ${callback} in class ${target.constructor.name}` | |
); | |
} | |
// handle change to main variable | |
Object.defineProperty(target, key, { | |
set: function (value) { | |
// change status of "isFirstChange" | |
this[isFirstChangeKey] = this[isFirstChangeKey] === undefined; | |
// No operation if new value is same as old value | |
if (!this[isFirstChangeKey] && this[cachedValueKey] === value) { | |
return; | |
} | |
const that = this; | |
const oldValue = this[cachedValueKey]; | |
//this[cachedValueKey] = value; | |
if (typeof value === 'object') { | |
this[cachedValueKey] = createObjectProxy(value, callBackFn, [key]); | |
} else { | |
this[cachedValueKey] = value; | |
} | |
const simpleChange: ChangeDetails<T> = { | |
propertyKey: key, | |
firstChange: this[isFirstChangeKey], | |
previousValue: oldValue, | |
currentValue: this[cachedValueKey], | |
isFirstChange: () => this[isFirstChangeKey], | |
}; | |
callBackFn.call(this, this[cachedValueKey], simpleChange); | |
}, | |
get: function () { | |
return this[cachedValueKey]; | |
}, | |
}); | |
}; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// https://stackblitz.com/edit/stackblitz-starters-fylgaw?file=src%2Fmain.ts | |
@Component({ | |
selector: 'my-app', | |
standalone: true, | |
imports: [CommonModule], | |
template: ` | |
<h1>{{ value }}</h1> | |
<button (click)="incNo()"> increase </button> | |
`, | |
}) | |
export class App extends AppMixin { | |
name = 'Angular'; | |
@OnChange('valueChanged') | |
value = 0; | |
@OnChange('valueChanged') | |
value2 = { | |
number: 0, | |
nested: { | |
valuex: 2, | |
}, | |
}; | |
incNo() { | |
this.value += 1; | |
this.value2.number += 5; | |
this.value2.nested.valuex += 2; | |
} | |
valueChanged(value: number, change: ChangeDetails<number>) { | |
console.log( | |
`${change!.propertyKey.toString()} is changed from ${change!.previousValue} to ${value}` | |
); | |
// Output will be:: | |
// value is changed from 0 to 1 | |
// value2.number is changed from 0 to 5 | |
// value2.nested.valuex is changed from 2 to 4 | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment