Angular Declarable Is Not Standalone
ID |
javascript.angular_standalone |
Severity |
info |
Remediation Complexity |
medium |
Remediation Risk |
low |
Remediation Effort |
medium |
Resource |
Best Practice |
Language |
JavaScript |
Tags |
angular, best-practice, framework, modularity |
Description
Reports an Angular @Component, @Directive or @Pipe whose metadata does not declare standalone: true:
// Bad — usable only through the module that declares it.
@Component({ selector: "app-legacy", template: "" })
export class LegacyComponent { }
// Bad — the same shape, written on purpose.
@Component({ selector: "app-optout", template: "", standalone: false })
export class OptOutComponent { }
// Good
@Component({ selector: "app-root", template: "", standalone: true, imports: [CommonModule] })
export class AppComponent { }
Rationale
A non-standalone declarable cannot be used on its own. It has to appear in the declarations of exactly one NgModule, and every consumer has to import that module. Three consequences follow:
-
A component’s own file no longer says what it depends on — the dependency graph lives in the modules instead of in the things that actually depend on each other.
-
Moving a component between features means editing two modules, and listing it in two modules is a runtime error rather than a compile-time one.
-
Nothing can load it in isolation, so a test or a lazy route has to pull in the module and everything else that module declares.
A standalone declarable carries its own imports, which makes it independently testable, lazily loadable, and readable in one file.
Remediation
Add standalone: true and move what the template needs into the declarable’s own imports:
@Component({
selector: "app-legacy",
template: "",
standalone: true,
imports: [CommonModule, RouterLink]
})
export class LegacyComponent { }
Then drop the class from the module’s declarations and add it to that module’s imports for as long as the module still exists.