or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

accessibility-rules.mdangular-framework.mdcomponent-rules.mddecorator-metadata-rules.mddirective-rules.mdindex.mdinput-output-rules.mdlifecycle-rules.mdpipe-rules.mdstyle-architecture-rules.mdtemplate-rules.md
tile.json

input-output-rules.mddocs/

Input/Output Rules

Angular component and directive input/output validation rules that enforce best practices for property binding and event handling.

Capabilities

No Input Prefix Rule

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
}

No Input Rename Rule

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>

No Inputs Metadata Property Rule

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
}

No Output On Prefix Rule

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>

No Output Rename Rule

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
}

No Outputs Metadata Property Rule

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[];
}

No Output Native Rule

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
}

Input/Output Best Practices

Input Property Design

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';
}

Output Event Design

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;
}

Two-way Binding Pattern

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>

Rule Configuration

Example Input/Output Rules Configuration

{
  "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
  }
}

Common Patterns

Form Component Integration

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>();
}

Data Display Components

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>();
}