AbsoluteJS

absolute/angular-one-feature-per-file

Disallow defining more than one Angular feature class (@Component, @Directive, @Pipe, @Injectable, @NgModule) per file.

AngularProblem
View on GitHub

#Rule Details

Mirrors the Angular Style Guide’s Single Responsibility / "Rule of One": every Angular feature gets its own file. The rule counts classes carrying a class-level feature decorator — @Component, @Directive, @Pipe, @Injectable, or @NgModule. The first one in a file is fine; every additional feature class is reported.

Member decorators such as @Input, @Output, and @HostListener are not features and never count, so a fully decorated component with many inputs is still a single feature. Undecorated helper classes are ignored entirely, so you can keep small private classes next to the feature they support.

Decorators are matched by their local name, so an aliased import (import { Component as NgComponent }) is not recognized. Spec and Storybook files routinely declare stub or host classes beside the subject under test — disable the rule for those globs with an override.

#Examples

Incorrect
TS
@Component({ selector: 'app-card', template: '' })
class CardComponent {}

@Injectable({ providedIn: 'root' })
class CardService {} // flagged: second feature class in the file
TS
@Pipe({ name: 'currency' })
class CurrencyPipe {}

@Directive({ selector: '[appHighlight]' })
class HighlightDirective {} // flagged
Correct

One feature class — member decorators are fine

TS
@Component({ selector: 'app-card', template: '' })
export class CardComponent {
	@Input() title = '';
	@Output() select = new EventEmitter<string>();
	@HostListener('click') onClick() {}
}

Plain helper classes are ignored

TS
@Component({ selector: 'app-card', template: '' })
export class CardComponent {}

// Undecorated helpers can live alongside it
class CardLayout {}
class CardData {}

#Configuration

Enable this rule in your ESLint configuration:

JS
// eslint.config.js
export default [
	{
		rules: {
			'absolute/angular-one-feature-per-file': 'error'
		}
	},
	{
		// Specs and stories legitimately co-locate stub classes
		files: ['**/*.spec.ts', '**/*.stories.ts'],
		rules: {
			'absolute/angular-one-feature-per-file': 'off'
		}
	}
];

#When Not To Use It

If you intentionally co-locate several small features in one file — for example test doubles, Storybook host components, or a tightly coupled directive plus its module — turn the rule off for those files with an ESLint override rather than disabling it project-wide.

#Resources