Files
marco/.agents/skills/ember-best-practices/rules/advanced-helpers.md

4.2 KiB

title, impact, impactDescription, tags
title impact impactDescription tags
Use Helper Functions for Reusable Logic LOW-MEDIUM Better code reuse and testability helpers, templates, reusability, advanced

Use Helper Functions for Reusable Logic

Extract reusable template logic into helper functions that can be tested independently and used across templates.

Incorrect (logic duplicated in components):

// app/components/user-card.js
class UserCard extends Component {
  get formattedDate() {
    const date = new Date(this.args.user.createdAt);
    const now = new Date();
    const diffMs = now - date;
    const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

    if (diffDays === 0) return 'Today';
    if (diffDays === 1) return 'Yesterday';
    if (diffDays < 7) return `${diffDays} days ago`;
    return date.toLocaleDateString();
  }
}

// app/components/post-card.js - same logic duplicated!
class PostCard extends Component {
  get formattedDate() {
    // Same implementation...
  }
}

Correct (reusable helper):

For single-use helpers, keep them in the same file as the component:

// app/components/post-list.gjs
import Component from '@glimmer/component';

// Helper co-located in same file
function formatRelativeDate(date) {
  const dateObj = new Date(date);
  const now = new Date();
  const diffMs = now - dateObj;
  const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

  if (diffDays === 0) return 'Today';
  if (diffDays === 1) return 'Yesterday';
  if (diffDays < 7) return `${diffDays} days ago`;
  return dateObj.toLocaleDateString();
}

class PostList extends Component {
  <template>
    {{#each @posts as |post|}}
      <article>
        <h2>{{post.title}}</h2>
        <time>{{formatRelativeDate post.createdAt}}</time>
      </article>
    {{/each}}
  </template>
}

For helpers shared across multiple components in a feature, use a subdirectory:

// app/components/blog/format-relative-date.js
export function formatRelativeDate(date) {
  const dateObj = new Date(date);
  const now = new Date();
  const diffMs = now - dateObj;
  const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

  if (diffDays === 0) return 'Today';
  if (diffDays === 1) return 'Yesterday';
  if (diffDays < 7) return `${diffDays} days ago`;
  return dateObj.toLocaleDateString();
}

Alternative (shared helper in utils):

For truly shared helpers used across the whole app, use app/utils/:

// app/utils/format-relative-date.js
// Flat structure - use subpath-imports in package.json for nicer imports if needed
export function formatRelativeDate(date) {
  const dateObj = new Date(date);
  const now = new Date();
  const diffMs = now - dateObj;
  const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24));

  if (diffDays === 0) return 'Today';
  if (diffDays === 1) return 'Yesterday';
  if (diffDays < 7) return `${diffDays} days ago`;
  return dateObj.toLocaleDateString();
}

Note: Keep utils flat (app/utils/format-relative-date.js), not nested (app/utils/date/format-relative-date.js). If you need cleaner top-level imports, configure subpath-imports in package.json instead of nesting files.

// app/components/user-card.gjs
import { formatRelativeDate } from '../utils/format-relative-date';

<template>
  <p>Joined: {{formatRelativeDate @user.createdAt}}</p>
</template>
// app/components/post-card.gjs
import { formatRelativeDate } from '../utils/format-relative-date';

<template>
  <p>Posted: {{formatRelativeDate @post.createdAt}}</p>
</template>

For helpers with state, use class-based helpers:

// app/utils/helpers/format-currency.js
export class FormatCurrencyHelper {
  constructor(owner) {
    this.intl = owner.lookup('service:intl');
  }

  compute(amount, { currency = 'USD' } = {}) {
    return this.intl.formatNumber(amount, {
      style: 'currency',
      currency,
    });
  }
}

Common helpers to create:

  • Date/time formatting
  • Number formatting
  • String manipulation
  • Array operations
  • Conditional logic

Helpers promote code reuse, are easier to test, and keep components focused on behavior.

Reference: Ember Helpers