Docs
Frameworks
Angular

Angular

FeatureDrop's Angular adapter provides a service-based API compatible with Angular 17+ signals and dependency injection.

Install

npm install featuredrop

No Angular peer dependency is required. The adapter uses a SignalLike abstraction that you wire to Angular's signal().

Quick setup

Create a service that wraps FeatureDrop:

featuredrop.service.ts
import { Injectable, signal } from '@angular/core'
import { FeatureDropService } from 'featuredrop/angular'
import { LocalStorageAdapter } from 'featuredrop'
import manifest from './features.json'
 
@Injectable({ providedIn: 'root' })
export class AppFeatureDropService extends FeatureDropService {
  constructor() {
    super({
      manifest,
      storage: new LocalStorageAdapter(),
      createSignal: (initial) => {
        const s = signal(initial)
        const accessor = () => s()
        accessor.set = (v: typeof initial) => s.set(v)
        return accessor
      }
    })
  }
}

Using in components

sidebar.component.ts
import { Component, inject } from '@angular/core'
import { AppFeatureDropService } from './featuredrop.service'
 
@Component({
  selector: 'app-sidebar',
  template: `
    <nav>
      <a routerLink="/settings">
        Settings
        @if (fd.isNew('dark-mode')) {
          <span class="badge">New</span>
        }
      </a>
    </nav>
    <p>{{ fd.newCount() }} new features</p>
    <button (click)="fd.dismissAll()">Mark all read</button>
  `
})
export class SidebarComponent {
  fd = inject(AppFeatureDropService)
}

Factory function

For more control, use createFeatureDropService() directly:

import { createFeatureDropService } from 'featuredrop/angular'
import { signal } from '@angular/core'
 
const service = createFeatureDropService({
  manifest,
  storage: new LocalStorageAdapter(),
  createSignal: (initial) => {
    const s = signal(initial)
    const accessor = () => s()
    accessor.set = (v: typeof initial) => s.set(v)
    return accessor
  }
})

API reference

FeatureDropService

Method / PropertyTypeDescription
newFeaturesSignalLike<FeatureEntry[]>Reactive list of new features
newCount()numberCurrent count of new features
newFeaturesSorted()FeatureEntry[]New features sorted by date
refresh()voidRe-compute from storage
dismiss(id)voidMark one feature dismissed
dismissAll()Promise<void>Mark all features dismissed
isNew(sidebarKey)booleanCheck if a feature is new
getFeature(sidebarKey)FeatureEntry | undefinedLook up a feature by ID

Options

OptionTypeRequiredDescription
manifestFeatureManifestYesFeature entries array
storageStorageAdapterYesPersistence adapter
createSignalCreateSignalLikeFnYesSignal factory (Angular's signal())
analyticsAnalyticsCallbacksNoTrack events
userContextUserContextNoAudience segmentation
matchAudienceAudienceMatchFnNoCustom audience matcher
appVersionstringNoVersion for version-pinning

The SignalLike pattern

The adapter does not import @angular/core directly. Instead, it defines a minimal SignalLike<T> interface:

interface SignalLike<T> {
  (): T       // Read the value
  set: (value: T) => void  // Write the value
}

You bridge Angular's signal() to this interface in the createSignal option. This keeps the adapter framework-agnostic at the package level while supporting Angular's reactivity.

User segmentation

@Injectable({ providedIn: 'root' })
export class AppFeatureDropService extends FeatureDropService {
  constructor() {
    super({
      manifest,
      storage: new LocalStorageAdapter(),
      createSignal: createSignalBridge,
      userContext: {
        plan: 'pro',
        role: 'admin',
        region: 'eu'
      }
    })
  }
}

TypeScript

Full .d.ts declarations are generated:

import type {
  AngularFeatureDropOptions,
  AngularFeatureDropService,
  SignalLike,
  CreateSignalLikeFn
} from 'featuredrop/angular'