Skip to content

Commit dc3aaff

Browse files
authored
Merge pull request #74 from cloudnc/fix/ensure-ng-on-changes-original-hook-defined-when-using-the-same-observable
fix: ensure the definition of the hook ngOnChanges if the corresponding observable is used. See https://stackoverflow.com/a/77930589/2398593
2 parents d71ea1f + 492f013 commit dc3aaff

File tree

4 files changed

+62
-5
lines changed

4 files changed

+62
-5
lines changed

README.md

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -85,15 +85,15 @@ Here's an example component that hooks onto the full set of available hooks.
8585
```ts
8686
// ./src/app/child/child.component.ts
8787
88-
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
88+
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
8989
import { getObservableLifecycle } from 'ngx-observable-lifecycle';
9090
9191
@Component({
9292
selector: 'app-child',
9393
templateUrl: './child.component.html',
9494
changeDetection: ChangeDetectionStrategy.OnPush,
9595
})
96-
export class ChildComponent {
96+
export class ChildComponent implements OnChanges {
9797
@Input() input1: number | undefined | null;
9898
@Input() input2: string | undefined | null;
9999
@@ -134,6 +134,11 @@ export class ChildComponent {
134134
changes.input2?.previousValue; // `string | null | undefined`
135135
});
136136
}
137+
138+
// when using the ngOnChanges hook, you have to define the hook in your class even if it's empty
139+
// See https://stackoverflow.com/a/77930589/2398593 for more info
140+
// eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
141+
public ngOnChanges() {}
137142
}
138143
139144
```

projects/ngx-observable-lifecycle/src/lib/ngx-observable-lifecycle.spec.ts

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, OnDestroy, OnInit } from '@angular/core';
1+
import { Component, OnChanges, OnDestroy, OnInit } from '@angular/core';
22
import { isObservable } from 'rxjs';
33
import { getObservableLifecycle } from './ngx-observable-lifecycle';
44

@@ -106,5 +106,46 @@ describe('ngx-observable-lifecycle', () => {
106106

107107
expect(originalOnDestroySpy).toHaveBeenCalled();
108108
});
109+
110+
// see https://stackoverflow.com/a/77930589/2398593
111+
it(`should throw if ngOnChanges isn't defined in the component if ngOnChanges observable is used`, () => {
112+
class LocalTestComponent {
113+
constructor() {
114+
// all except ngOnChanges
115+
// even without having the original `ngOnChanges` hook it should be ok
116+
const {
117+
ngOnInit,
118+
ngDoCheck,
119+
ngAfterContentInit,
120+
ngAfterContentChecked,
121+
ngAfterViewInit,
122+
ngAfterViewChecked,
123+
ngOnDestroy,
124+
} = getObservableLifecycle(this);
125+
}
126+
}
127+
128+
expect(() => new LocalTestComponent()).not.toThrowError();
129+
130+
class LocalTestWithNgOnChangesNoOriginalHookComponent {
131+
constructor() {
132+
// without having the original `ngOnChanges` hook it should throw
133+
const { ngOnChanges } = getObservableLifecycle(this);
134+
}
135+
}
136+
expect(() => new LocalTestWithNgOnChangesNoOriginalHookComponent()).toThrowError(
137+
`When using the ngOnChanges hook, you have to define the hook in your class even if it's empty. See https://stackoverflow.com/a/77930589/2398593`,
138+
);
139+
140+
class LocalTestWithNgOnChangesAndOriginalHookComponent implements OnChanges {
141+
constructor() {
142+
// when we have the original `ngOnChanges` hook it should **not** throw
143+
const { ngOnChanges } = getObservableLifecycle(this);
144+
}
145+
146+
public ngOnChanges(): void {}
147+
}
148+
expect(() => new LocalTestWithNgOnChangesAndOriginalHookComponent()).not.toThrowError();
149+
});
109150
});
110151
});

projects/ngx-observable-lifecycle/src/lib/ngx-observable-lifecycle.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,12 @@ type PatchedComponentInstance<Hooks extends LifecycleHookKey = any> = Pick<AllHo
6767
};
6868

6969
function getSubjectForHook(componentInstance: PatchedComponentInstance, hook: LifecycleHookKey): Subject<void> {
70+
if (hook === 'ngOnChanges' && !componentInstance.constructor.prototype[hook]) {
71+
throw new Error(
72+
`When using the ngOnChanges hook, you have to define the hook in your class even if it's empty. See https://stackoverflow.com/a/77930589/2398593`,
73+
);
74+
}
75+
7076
if (!componentInstance[hookSubject]) {
7177
componentInstance[hookSubject] = {};
7278
}

src/app/child/child.component.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
1+
import { ChangeDetectionStrategy, Component, Input, OnChanges } from '@angular/core';
22
import { getObservableLifecycle } from 'ngx-observable-lifecycle';
33

44
@Component({
55
selector: 'app-child',
66
templateUrl: './child.component.html',
77
changeDetection: ChangeDetectionStrategy.OnPush,
88
})
9-
export class ChildComponent {
9+
export class ChildComponent implements OnChanges {
1010
@Input() input1: number | undefined | null;
1111
@Input() input2: string | undefined | null;
1212

@@ -47,4 +47,9 @@ export class ChildComponent {
4747
changes.input2?.previousValue; // `string | null | undefined`
4848
});
4949
}
50+
51+
// when using the ngOnChanges hook, you have to define the hook in your class even if it's empty
52+
// See https://stackoverflow.com/a/77930589/2398593 for more info
53+
// eslint-disable-next-line @angular-eslint/no-empty-lifecycle-method
54+
public ngOnChanges() {}
5055
}

0 commit comments

Comments
 (0)