Skip to main content

Installation

npm install @sanity-labs/logo-soup
Logo Soup has optional peer dependencies. For Angular:
npm install @angular/core@^19
Requires Angular 19+ for standalone components and signal-based APIs

Quick Start

LogoSoupService is an injectable service that manages logo processing with Angular signals:
import { Component, input, effect, inject } from "@angular/core";
import { LogoSoupService } from "@sanity-labs/logo-soup/angular";

@Component({
  selector: "logo-strip",
  standalone: true,
  providers: [LogoSoupService],
  template: `
    @for (logo of service.state().normalizedLogos; track logo.src) {
      <img
        [src]="logo.src"
        [alt]="logo.alt"
        [width]="logo.normalizedWidth"
        [height]="logo.normalizedHeight"
      />
    }
  `,
})
export class LogoStripComponent {
  protected service = inject(LogoSoupService);
  logos = input.required<string[]>();

  constructor() {
    effect(() => {
      this.service.process({ logos: this.logos() });
    });
  }
}

Service API

LogoSoupService

Injectable: Must be provided at component level via providers Properties:
class LogoSoupService {
  // Readonly signal containing current state
  readonly state: Signal<LogoSoupState>;
  
  // Trigger processing with new options
  process(options: ProcessOptions): void;
}
State Type:
type LogoSoupState = {
  status: "idle" | "loading" | "ready" | "error";
  normalizedLogos: NormalizedLogo[];
  error: Error | null;
};

Process Options

All standard processing options are supported:
type ProcessOptions = {
  logos: (string | LogoSource)[];
  baseSize?: number;
  scaleFactor?: number;
  contrastThreshold?: number;
  densityAware?: boolean;
  densityFactor?: number;
  cropToContent?: boolean;
  backgroundColor?: BackgroundColor;
};

Automatic Cleanup

The service automatically cleans up when the component is destroyed using DestroyRef:
@Injectable()
export class LogoSoupService {
  private engine = createEngine();
  private destroyRef = inject(DestroyRef);

  constructor() {
    const unsubscribe = this.engine.subscribe(() => {
      this._state.set(this.engine.getSnapshot());
    });

    this.destroyRef.onDestroy(() => {
      unsubscribe();
      this.engine.destroy();
    });
  }
}

Examples

import { Component, input, effect, inject } from "@angular/core";
import { LogoSoupService } from "@sanity-labs/logo-soup/angular";
import { getVisualCenterTransform } from "@sanity-labs/logo-soup";
import { NgStyle } from "@angular/common";

@Component({
  selector: "logo-strip",
  standalone: true,
  imports: [NgStyle],
  providers: [LogoSoupService],
  template: `
    <div class="logo-container">
      @for (logo of service.state().normalizedLogos; track logo.src) {
        <img
          [src]="logo.src"
          [alt]="logo.alt"
          [width]="logo.normalizedWidth"
          [height]="logo.normalizedHeight"
          [ngStyle]="{
            transform: getTransform(logo)
          }"
        />
      }
    </div>
  `,
  styles: `
    .logo-container {
      display: flex;
      gap: 2rem;
      justify-content: center;
    }
  `,
})
export class LogoStripComponent {
  protected service = inject(LogoSoupService);
  logos = input.required<Array<{ src: string; alt: string }>>();

  constructor() {
    effect(() => {
      this.service.process({
        logos: this.logos(),
        baseSize: 48,
      });
    });
  }

  protected getTransform(logo: any): string {
    return getVisualCenterTransform(logo, "visual-center-y") || "none";
  }
}

TypeScript

All Angular exports are fully typed:
import { LogoSoupService } from "@sanity-labs/logo-soup/angular";

import type {
  ProcessOptions,
  LogoSoupState,
  LogoSource,
  NormalizedLogo,
  AlignmentMode,
  BackgroundColor,
} from "@sanity-labs/logo-soup";

Service Scope

Always provide LogoSoupService at the component level, not at the module or root level. Each component instance needs its own service instance.
@Component({
  selector: "my-component",
  providers: [LogoSoupService], // ✅ Component-scoped
  // ...
})
// ❌ Don't do this
@NgModule({
  providers: [LogoSoupService], // Shared across all components
})

See Also

Build docs developers (and LLMs) love