Angular component and directive input/output validation rules that enforce best practices for property binding and event handling.
Prevents specific prefixes on input properties that could cause confusion.
/**
* Prevents specific prefixes on @Input properties
* Avoids naming conflicts and maintains clear property naming
*/
export class NoInputPrefixRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
TSLint configuration:
{
"rules": {
"no-input-prefix": [true, "is", "has", "should"]
}
}Bad input prefixes:
export class UserComponent {
@Input() isActive: boolean; // ❌ 'is' prefix
@Input() hasPermissions: boolean; // ❌ 'has' prefix
@Input() shouldShow: boolean; // ❌ 'should' prefix
}Good input names:
export class UserComponent {
@Input() active: boolean; // ✅ Clear, direct property name
@Input() permissions: string[]; // ✅ Noun describing the data
@Input() visible: boolean; // ✅ Descriptive without problematic prefix
}Prevents renaming of input properties in the @Input decorator.
/**
* Prevents renaming @Input properties in the decorator
* Maintains consistency between property names and template usage
*/
export class NoInputRenameRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad input renaming:
export class UserComponent {
@Input('userName') name: string; // ❌ Renamed input
@Input('isActive') active: boolean; // ❌ Renamed input
}Good input naming:
export class UserComponent {
@Input() userName: string; // ✅ Property name matches input name
@Input() active: boolean; // ✅ No renaming
}Template usage:
<app-user [userName]="user.name" [active]="user.isActive"></app-user>Prevents defining inputs in the component/directive metadata instead of using @Input decorator.
/**
* Prevents defining inputs in component metadata
* Enforces use of @Input decorator for better type safety and clarity
*/
export class NoInputsMetadataPropertyRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad metadata inputs:
@Component({
selector: 'app-user',
template: '...',
inputs: ['name', 'active'] // ❌ Inputs in metadata
})
export class UserComponent {
name: string;
active: boolean;
}Good decorator inputs:
@Component({
selector: 'app-user',
template: '...'
})
export class UserComponent {
@Input() name: string; // ✅ @Input decorator
@Input() active: boolean; // ✅ @Input decorator
}Prevents "on" prefix on output event properties.
/**
* Prevents 'on' prefix on @Output event properties
* Follows Angular naming conventions for event properties
*/
export class NoOutputOnPrefixRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad output prefixes:
export class UserComponent {
@Output() onClick = new EventEmitter<void>(); // ❌ 'on' prefix
@Output() onSave = new EventEmitter<User>(); // ❌ 'on' prefix
@Output() onDelete = new EventEmitter<number>(); // ❌ 'on' prefix
}Good output names:
export class UserComponent {
@Output() click = new EventEmitter<void>(); // ✅ Direct event name
@Output() save = new EventEmitter<User>(); // ✅ Action-based name
@Output() delete = new EventEmitter<number>(); // ✅ Clear event name
}Template usage:
<app-user (click)="handleClick()" (save)="handleSave($event)" (delete)="handleDelete($event)">
</app-user>Prevents renaming of output properties in the @Output decorator.
/**
* Prevents renaming @Output properties in the decorator
* Maintains consistency between property names and template usage
*/
export class NoOutputRenameRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad output renaming:
export class UserComponent {
@Output('userClick') click = new EventEmitter<void>(); // ❌ Renamed output
@Output('saveUser') save = new EventEmitter<User>(); // ❌ Renamed output
}Good output naming:
export class UserComponent {
@Output() userClick = new EventEmitter<void>(); // ✅ Property name matches output name
@Output() save = new EventEmitter<User>(); // ✅ No renaming
}Prevents defining outputs in the component/directive metadata instead of using @Output decorator.
/**
* Prevents defining outputs in component metadata
* Enforces use of @Output decorator for better type safety and clarity
*/
export class NoOutputsMetadataPropertyRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Prevents using native DOM event names as output properties.
/**
* Prevents using native DOM event names as @Output properties
* Avoids conflicts with built-in DOM events
*/
export class NoOutputNativeRule extends Lint.Rules.AbstractRule {
static readonly metadata: Lint.IRuleMetadata;
static readonly FAILURE_STRING: string;
apply(sourceFile: ts.SourceFile): Lint.RuleFailure[];
}Usage Examples:
Bad native event names:
export class CustomComponent {
@Output() click = new EventEmitter<void>(); // ❌ Native DOM event
@Output() focus = new EventEmitter<void>(); // ❌ Native DOM event
@Output() blur = new EventEmitter<void>(); // ❌ Native DOM event
@Output() change = new EventEmitter<any>(); // ❌ Native DOM event
}Good custom event names:
export class CustomComponent {
@Output() itemClick = new EventEmitter<void>(); // ✅ Custom event name
@Output() itemSelected = new EventEmitter<any>(); // ✅ Descriptive custom name
@Output() valueChanged = new EventEmitter<any>(); // ✅ Custom variation
@Output() customAction = new EventEmitter<void>(); // ✅ Unique event name
}export class DataComponent {
// ✅ Simple primitive inputs
@Input() title: string;
@Input() count: number;
@Input() enabled: boolean;
// ✅ Complex object inputs with proper typing
@Input() user: User;
@Input() config: ComponentConfig;
// ✅ Optional inputs with default values
@Input() size: 'small' | 'medium' | 'large' = 'medium';
@Input() theme: string = 'default';
}export class DataComponent {
// ✅ Simple event notifications
@Output() loaded = new EventEmitter<void>();
@Output() error = new EventEmitter<string>();
// ✅ Events with meaningful data
@Output() itemSelected = new EventEmitter<User>();
@Output() dataChanged = new EventEmitter<DataChangeEvent>();
// ✅ Action-based events
@Output() save = new EventEmitter<User>();
@Output() cancel = new EventEmitter<void>();
@Output() delete = new EventEmitter<number>();
}
interface DataChangeEvent {
oldValue: any;
newValue: any;
field: string;
}export class CustomInputComponent {
@Input() value: string;
@Output() valueChange = new EventEmitter<string>();
// Internal method to emit changes
onValueChange(newValue: string): void {
this.value = newValue;
this.valueChange.emit(newValue);
}
}Template usage:
<app-custom-input [(value)]="formData.name"></app-custom-input>{
"rules": {
"no-input-prefix": [true, "is", "has", "should"],
"no-input-rename": true,
"no-inputs-metadata-property": true,
"no-output-on-prefix": true,
"no-output-rename": true,
"no-outputs-metadata-property": true,
"no-output-native": true
}
}export class FormFieldComponent implements ControlValueAccessor {
@Input() label: string;
@Input() placeholder: string;
@Input() disabled: boolean = false;
@Input() required: boolean = false;
@Output() focus = new EventEmitter<void>(); // ❌ Native event name
@Output() fieldFocus = new EventEmitter<void>(); // ✅ Custom name
@Output() valueChange = new EventEmitter<any>();
@Output() validationChange = new EventEmitter<boolean>();
}export class DataTableComponent {
@Input() data: any[];
@Input() columns: TableColumn[];
@Input() sortable: boolean = true;
@Input() filterable: boolean = false;
@Output() rowSelected = new EventEmitter<any>();
@Output() sortChanged = new EventEmitter<SortEvent>();
@Output() filterChanged = new EventEmitter<FilterEvent>();
}