Feed

The <smart-feed> component renders a vertical timeline of activity events with optional title, description, per-event icon/avatar, link, timestamp, description, nested comments, an empty state, an optional comment-submit slot, and a bottom footer slot. It follows the Base + Standard + Wrapper pattern with an InjectionToken-based extension mechanism. The abstract FeedBaseComponent defines the shared API — optional IFeedOptions and cssClass (alias class). FeedStandardComponent is a barebones placeholder concrete implementation. FeedComponent is the public wrapper that renders FeedStandardComponent by default and accepts a custom replacement via FEED_STANDARD_COMPONENT_TOKEN.


Usage

<smart-feed [options]="options" />

Components

FeedComponent (<smart-feed>)

Main wrapper component. Renders FeedStandardComponent by default. When FEED_STANDARD_COMPONENT_TOKEN is provided, renders the injected component via NgComponentOutlet.

FeedStandardComponent (<smart-feed-standard>)

Barebones placeholder concrete implementation. Renders a wrapper <div> containing an optional <h3 class="title">, optional <p class="description">, and a <ol role="list"> with one <li class="event"> per event. Each event renders the icon/avatar (icon template wins over avatar URL), an optional <time class="timestamp">, the title (rendered as <a class="title"> if href is provided, otherwise <span class="title">), an optional description, and a nested <ul class="comments"> with one <li class="comment"> per comment (avatar, author name, optional timestamp, content). When events list is empty, the optional emptyTpl is rendered inside <div class="empty">. An optional commentSubmitTpl renders inside <div class="comment-submit">. A bottom footerTpl renders inside <div class="footer">. The external cssClass is applied to the root wrapper. It does not include any visual styling — it exists solely as the default structural placeholder until a custom implementation is registered through the token.

FeedPresetComponent (<smart-feed-preset>)

Styled variation that extends FeedBaseComponent and is a drop-in replacement for FeedStandardComponent. Register it via FEED_STANDARD_COMPONENT_TOKEN to restyle every <smart-feed>, or use the <smart-feed-preset> selector directly. It renders the Preline timeline look in vanilla Tailwind: a vertical rail (drawn with an after: pseudo-element, hidden on the last item) with a per-event circular marker, a left-hand side timestamp column, the event title (rendered as a link when href is set), an event description, and nested comments shown as Preline-style author rows. The marker chooses iconTpl first, then avatarUrl (image), then a default dot. Comments render the author avatar when authorAvatarUrl is set, otherwise an initials fallback derived from the first letter of authorName. The emptyTpl, commentSubmitTpl, and footerTpl slots are honored. All classes are smart:-prefixed Tailwind with explicit dark: variants; the class recipes live in preset/preset-classes.util.ts.

Because FeedComponent renders injected components via NgComponentOutlet (which passes inputs by canonical name), FeedPresetComponent overrides cssClass as input<string>('') without the class alias. Bind it as [cssClass] when using the <smart-feed-preset> selector directly, or just pass class on <smart-feed> (the wrapper forwards it).

FeedBaseComponent (abstract)

Abstract base directive for extending custom feed implementations. Exposes options as an InputSignal<IFeedOptions | undefined> and cssClass as an InputSignal<string> (with alias class).

API

Inputs

InputTypeDefaultDescription
optionsInputSignal<IFeedOptions | undefined>-Optional configuration (title, description, events, slots, variant)
classInputSignal<string>''External CSS classes (alias for cssClass)

IFeedOptions

All properties are optional except IFeedEvent.title and IFeedComment.{authorName, content}. The default FeedStandardComponent consumes every property; a section is rendered only when its template/string is provided. Within an event, iconTpl takes precedence over avatarUrl when both are set. variant is a hint for custom implementations registered via the token.

FEED_STANDARD_COMPONENT_TOKEN

InjectionToken that allows replacing the default FeedStandardComponent with a custom implementation. Provide a Type<FeedBaseComponent> to override.

Extending the base class

import {
  ChangeDetectionStrategy,
  Component,
  computed,
  input,
  signal,
  ViewEncapsulation,
} from '@angular/core';

import {
  FEED_STANDARD_COMPONENT_TOKEN,
  FeedBaseComponent,
  FeedComponent,
  IFeedOptions,
} from '@smartsoft001/angular';

const COLLAPSED_COUNT = 2;

@Component({
  selector: 'docs-custom-feed',
  template: `
    <div [class]="containerClasses()">
      @if (options()?.title) {
        <h3 class="docs-feed__title">{{ options()?.title }}</h3>
      }

      <ol class="docs-feed__list">
        @for (event of visibleEvents(); track event.id ?? $index) {
          <li class="docs-feed__event">
            @if (event.timestamp) {
              <time class="docs-feed__time">{{ event.timestamp }}</time>
            }
            <span class="docs-feed__label">{{ event.title }}</span>
          </li>
        }
      </ol>

      @if (hasMore()) {
        <button
          type="button"
          class="docs-feed__more"
          (click)="expanded.set(true)"
        >
          Show all
        </button>
      }
    </div>
  `,
  encapsulation: ViewEncapsulation.None,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class CustomFeedComponent extends FeedBaseComponent {
  // NgComponentOutlet passes 'cssClass' by canonical name, not the 'class'
  // alias, so a feed registered through the token declares it explicitly.
  override cssClass = input<string>('');

  // A custom implementation is free to add its own state on top of the
  // options the wrapper forwards.
  expanded = signal(false);

  private events = computed(() => this.options()?.events ?? []);

  visibleEvents = computed(() =>
    this.expanded() ? this.events() : this.events().slice(0, COLLAPSED_COUNT),
  );

  hasMore = computed(() => this.events().length > this.visibleEvents().length);

  containerClasses = computed(() => {
    const classes = ['docs-feed'];
    const extra = this.cssClass();
    if (extra) classes.push(extra);
    return classes.join(' ');
  });
}

@Component({
  selector: 'docs-feed-custom-example',
  changeDetection: ChangeDetectionStrategy.OnPush,
  imports: [FeedComponent],
  // The token swaps the standard feed for the custom one everywhere below
  // this component, so consumers keep writing `<smart-feed>`.
  providers: [
    { provide: FEED_STANDARD_COMPONENT_TOKEN, useValue: CustomFeedComponent },
  ],
  template: `<smart-feed [options]="options" />`,
})
export class FeedCustomExampleComponent {
  options: IFeedOptions = {
    title: 'Application activity',
    events: [
      {
        id: 'applied',
        title: 'Applied to Front End Developer',
        timestamp: 'Sep 20',
      },
      {
        id: 'screening',
        title: 'Advanced to phone screening',
        timestamp: 'Sep 22',
      },
      {
        id: 'interview',
        title: 'Completed phone screening',
        timestamp: 'Sep 28',
      },
    ],
  };
}

Source

The component lives in packages/shared/angular/src/lib/components/feed and is documented for Claude Code by the angular-components-feed skill.