It's an alternative solution for DoCheck
if you want to have more customization and do something more manual. Of course, according to Günter Zöchbauer answer it's a kind of Observable
solution.
I use a decorator to tell TS that property must be detected, so let's get start it.
export class ChangeDetection {
private emitedChangeDetector: ChangeDetection;
private _value;
get value() {
return this._value;
}
/** @warning Don't use it */
__action: (value: any) => void;
/** @warning Don't use it */
__change(value: any) {
this._value = value;
this.__action && this.__action(value);
this.emitedChangeDetector?.__change &&
this.emitedChangeDetector.__change(this.emitedChangeDetector.value);
}
onChange<T = any>(action: (value: T) => void) {
this.__action = action;
return this;
}
emit(extendedObserver: ChangeDetection) {
this.emitedChangeDetector = extendedObserver;
}
}
// For manage list of changes
export class ChangeDetectionList {
changeDetectors: ChangeDetection[];
constructor(...changeDetectors: ChangeDetection[]) {
this.changeDetectors = changeDetectors;
}
onChange(callback: (data: any) => void): ChangeDetectionList {
this.changeDetectors.forEach((a: ChangeDetection) => a.onChange(callback));
return this;
}
emit(changeDetector: ChangeDetection): ChangeDetection {
this.changeDetectors.forEach((a: ChangeDetection) =>
a.emit(changeDetector)
);
return changeDetector;
}
}
/**
* @usageNotes{
* ```typescript
* @ChangeDetector()
* data : string
* ```
* Gives you a ChangeDetection object with the name of data$ that fires "onChange()" function when "data" is changed
*/
export function ChangeDetector(suffix: string = "$") {
return function (prototype: any, key: string | symbol) {
const changeDetectorName: string = `${key.toString()}${suffix}`;
if (!prototype[changeDetectorName]) {
Object.defineProperty(prototype, changeDetectorName, {
value: new ChangeDetection(),
});
const changeDetectorObject = prototype[changeDetectorName] as ChangeDetection;
Object.defineProperty(prototype, key, {
set(value: any) {
Object.defineProperty(this, key, {
get() {
return changeDetectorObject.value;
},
set(v: any) {
changeDetectorObject.__change(v);
},
enumerable: true,
});
this[key] = value;
},
enumerable: true,
configurable: true,
});
}
};
}
Now, It's easy to use. Just put @ChangeDetector()
decorator on each property what you want to detect or define a changeDetector
property to raise onChange
event when a property changes.
Here is an example show you how to use it.
export class PersonModel {
@ChangeDetector()
changeDetector: number;
changeDetector$: ChangeDetection; // It's just for intellisense, you can ignore it.
firstName: string;
lastName: string;
age: number;
constructor() {
this.age = 34;
this.changeDetector = 0;
}
}
export class ProfileModel {
@ChangeDetector('Listener')
person: PersonModel;
personListener: ChangeDetection; // It's just for intellisense, you can ignore it.
constructor() {
this.person = new PersonModel();
}
}
export class ProfileService {
profile: ProfileModel;
constructor() {
this.profile = new ProfileModel();
// calls 'profileChanged()' when 'person' is changed
this.profile.personListener.onChange((_) => this.profileChanged());
}
setProfile() {
Object.assign(this.profile.person, { firstName: 'Pedram', lastName: 'Ahmadpour' });
this.profile.person.changeDetector++;
}
private profileChanged() {
console.log('profile is', JSON.stringify(this.profile));
}
}
export class ProfileComponent {
constructor(private profileService: ProfileService) {
// Now, profileService.profile listen to its own child changes
this.profileService.profile.person.changeDetector$.emit(profileService.profile.personListener);
this.profileService.setProfile();
}
happyBirthday() {
this.profileService.profile.person.age = 35;
console.log('Happy birthday');
this.profileService.profile.person.changeDetector++;
}
}
Or in real Angular project:
@Input()
@ChangeDetector()
data: ProfileModel;
data$: ChangeDetection;
And for the test:
const profileService = new ProfileService();
const profileComponent = new ProfileComponent(profileService);
profileComponent.happyBirthday();