Open-Source Wikis

/

Angular

/

How to contribute

/

Patterns and conventions

angular/angular

Patterns and conventions

What review enforces. The canonical doc is contributing-docs/coding-standards.md; this page summarizes the patterns most commonly cited in PR feedback.

Code style

  • Base style: Google JavaScript / TypeScript Style Guide.
  • Wrap at 100 columns.
  • Format with prettier. CI fails if pnpm ng-dev format changed --check fails.
  • Lint with pnpm lint (tslint + format-check). The tslint ruleset is intentional — replacement with ESLint has been considered but not adopted.

Comments

The team's published rule: comments that say what are nice; comments that say why are invaluable. Useless example:

// Set default tabindex.
if (!attributes['tabindex']) {
  element.setAttribute('tabindex', '-1');
}

Useful example:

// Unless the user specifies otherwise, the calendar should not be a tab stop.
// This prevents ngAria from overzealously adding a tabindex to anything with an ng-model.
if (!attributes['tabindex']) {
  element.setAttribute('tabindex', '-1');
}

JSDoc (/** */) for public API descriptions; // for explanations and asides.

API design

Avoid boolean arguments

Don't add boolean arguments that switch behavior; split into two functions. Exception: framework hot paths where a boolean parameter shaves bytes off the bundle.

// Avoid
function getTargetElement(createIfNotFound = false) {
  /* ... */
}

// Prefer
function getExistingTargetElement() {
  /* ... */
}
function createTargetElement() {
  /* ... */
}

Optional arguments

Use sparingly. Only when the API genuinely has an optional input — never for implementation convenience.

Getters and setters

Avoid getters/setters except:

  • on @Input properties that need a setter,
  • when API compatibility requires it,
  • never longer than three lines (extract to a method).

A getter without a setter is a readonly property in disguise. Use readonly directly.

TypeScript

  • Avoid any. Reach for a generic or unknown first.
  • const everywhere by default. Use let only where mutation is necessary. Avoid var.
  • Use readonly for class members that don't change.
  • try/catch only when handling a legitimately unexpected error. Each try/catch must be commented with the specific error and why prevention isn't possible.

Naming

  • Names describe what the code does, not how it's used. NonStoringRouteReuseStrategy over DefaultRouteReuseStrategy.
  • Prefer full words over abbreviations.
  • Booleans use is/has prefixes, except on @Input() which uses the natural English form.
  • Don't suffix Observables with $.
  • Classes: PascalCase. Don't end with Impl.
  • Interfaces: don't prefix with I, don't suffix with Interface.
  • Functions/methods: camelCase. Names describe the action performed (activateRipple() not handleClick()).
  • Constants and DI tokens: UPPER_SNAKE_CASE.
  • Test fixtures: descriptive names (FormGroupWithCheckboxAndRadios over Comp).

RxJS

  • Don't suffix Observables with $.
  • When importing of, alias it: import {of as observableOf} from 'rxjs'; to avoid clashing with the destructured of keyword in template syntax discussions.
  • Prefer signals for new reactive state. RxJS remains the standard for HTTP, router, and any observable stream that crosses time.

Iteration

Prefer for and for…of over Array.prototype.forEach. forEach makes debugger stepping harder and adds function-call overhead.

Errors and warnings

  • Use formatRuntimeError(code, message) from packages/core/src/errors.ts for runtime issues. The numeric code links the message to its angular.dev documentation page.
  • New error codes are allocated sequentially in the existing range. Don't reuse retired codes.
  • Throw RuntimeError for hard failures, log via console.warn for recoverable ones.

Test naming

// Prefer
describe('Router', () => {
  describe('with the default route reuse strategy', () => {
    it('should not reuse routes upon location change', () => {
      /* ... */
    });
  });
});

// Avoid
describe('Router', () => {
  describe('default strategy', () => {
    it('should work', () => {
      /* ... */
    });
  });
});

Test names should read as a sentence. The standard it('should …', …) form is enforced by review.

Public API surface

Adding or changing anything that lands in a public index.ts of a package requires:

  1. A JSDoc on the symbol describing what it does and what each parameter is.
  2. An update to the matching goldens/public-api/<package>.api.md (pnpm public-api:update).
  3. A BREAKING CHANGE: footer if the change is breaking.

Removing anything from the public API requires:

  1. A deprecation in a previous major (@deprecated JSDoc + DEPRECATED: footer in commit).
  2. An automated migration in packages/core/schematics/migrations/ if the change requires user code edits.
  3. The actual removal in the next major.

The full process is documented in contributing-docs/public-api-surface.md.

@experimental and @developerPreview

  • @developerPreview — a feature exposed for early adoption. Public API rules apply but the team can change it without a deprecation cycle.
  • @experimental — older marker, similar semantics, used less often now.

The full distinction is in contributing-docs/dev_preview_and_experimental.md.

Cross-cutting "do not" list

Things that come up in review repeatedly:

  • Don't import from another package's src/ directly. Cross-package imports must go through the public index.ts.
  • Don't use private to mean "internal to the framework". TypeScript's private is enforced at compile time only; framework-internal symbols use the ɵ prefix or the core_private_export.ts re-export pattern.
  • Don't add new .ngfactory references. They're View Engine residue.
  • Don't introduce new circular module imports. pnpm ts-circular-deps:check runs in CI.
  • Don't write it('works', …). Use a sentence.

Built by Factory AutoWiki from public repository content. It is a generated preview for codebase exploration, not source-maintained documentation.

Patterns and conventions – Angular wiki | Factory