Angular Lifecycle Hook In The Wrong Context
ID |
javascript.angular_lifecycle_context |
Severity |
high |
Remediation Complexity |
trivial |
Remediation Risk |
low |
Remediation Effort |
low |
Resource |
Dead Code |
Language |
JavaScript |
Tags |
angular, framework, reliability, unused-code |
Description
Reports an Angular change-detection lifecycle hook declared on a class Angular never calls it on — a service, a pipe or a module:
@Injectable({ providedIn: "root" })
export class DataService {
ngOnInit() { // FLAW — never runs
this.load();
}
ngOnDestroy() { // fine: the injector calls this one
this.close();
}
}
The seven hooks reported are ngOnInit, ngOnChanges, ngDoCheck, ngAfterContentInit, ngAfterContentChecked, ngAfterViewInit and ngAfterViewChecked, on a class decorated with @Injectable, @Pipe or @NgModule.
Rationale
Those seven hooks belong to the change-detection cycle, and change detection visits only what appears in a view: components and directives. A class the injector builds as a provider, a transformation or a module never enters that cycle.
The failure is silent. Nothing throws, nothing warns, the initialisation simply never happens — and the bug surfaces much later, somewhere else, as a value that was supposed to be loaded and is not. The method reads as live setup to every reviewer.
ngOnDestroy is the exception and is not reported: the injector calls it when the providing scope is torn down, which is the documented way for a service or a pipe to release a subscription.
Remediation
Decide which of the two the class is:
-
If the initialisation belongs to the service, run it from the constructor, or from an explicit method the consumer calls:
@Injectable({ providedIn: "root" })
export class DataService {
constructor() {
this.load();
}
}
-
If the class was meant to appear in a view, give it the decorator that says so:
@Component({ selector: "app-data", template: "" })
export class DataComponent {
ngOnInit() {
this.load();
}
}
Notes
A class carrying both a view decorator and a provider one is a component, so its hooks do run and it is left alone.
javascript.angular_lifecycle_interface asks a different question — whether a hook is backed by the matching implements clause. A hook can carry its interface and still sit on a class that never calls it.