npm package: @irv-labs/ngx-responsive-signals
Description: Signal-based responsive breakpoints for Angular
Author: irv-labs
Latest version: 1.0.1

# ngx-responsive-signals

Signal-based responsive breakpoints for Angular. Inject the service, call a method, get a `Signal<boolean>` — no RxJS, no subscriptions, no boilerplate.

---

## Requirements

- Angular 17+

---

## Installation

```bash
npm install @irv-labs/ngx-responsive-signals
```

---

## Quick Start

```typescript
import { ResponsiveService, Screen } from '@irv-labs/ngx-responsive-signals';

@Component({ ... })
export class MyComponent {
  private responsive = inject(ResponsiveService);

  isMobile    = this.responsive.is(Screen.Mobile);
  showSidebar = this.responsive.isAtLeast(Screen.Tablet);
  isCompact   = this.responsive.isAtMost(Screen.Tablet);
}
```

```html
@if (isMobile()) {
<app-mobile-nav />
} @else {
<app-desktop-nav />
}
```

---

## Breakpoints

| Screen         | Width         |
| -------------- | ------------- |
| `Mobile`       | <= 480px      |
| `Tablet`       | 481 – 768px   |
| `LargeTablet`  | 769 – 1024px  |
| `Desktop`      | 1025 – 1280px |
| `LargeDesktop` | > 1280px      |

---

## API

### `responsive.is(screen)`

Returns `Signal<boolean>` — true only when the current screen matches exactly.

```typescript
isMobile = this.responsive.is(Screen.Mobile);
isTablet = this.responsive.is(Screen.Tablet);
isDesktop = this.responsive.is(Screen.Desktop);
```

---

### `responsive.isAtLeast(screen)`

Returns `Signal<boolean>` — true when the current screen is that size or larger.

```typescript
showSidebar = this.responsive.isAtLeast(Screen.Tablet); // Tablet, LargeTablet, Desktop, LargeDesktop
fullLayout = this.responsive.isAtLeast(Screen.Desktop); // Desktop, LargeDesktop
```

---

### `responsive.isAtMost(screen)`

Returns `Signal<boolean>` — true when the current screen is that size or smaller.

```typescript
isCompact = this.responsive.isAtMost(Screen.Tablet); // Mobile, Tablet
stackedNav = this.responsive.isAtMost(Screen.LargeTablet); // Mobile, Tablet, LargeTablet
```

---

### `responsive.isLarger(screen)`

Returns `Signal<boolean>` — true when the current screen is strictly larger than the given one.

```typescript
aboveTablet = this.responsive.isLarger(Screen.Tablet); // LargeTablet, Desktop, LargeDesktop
```

---

### `responsive.isSmaller(screen)`

Returns `Signal<boolean>` — true when the current screen is strictly smaller than the given one.

```typescript
belowDesktop = this.responsive.isSmaller(Screen.Desktop); // Mobile, Tablet, LargeTablet
```

---

### `responsive.screen`

`Signal<Screen>` — the current screen value. Useful for the pipe or for derived logic.

```typescript
screen = this.responsive.screen;
```

---

## Pipe

Import `ResponsivePipe` in your component and pass `responsive.screen` as input. The argument follows the format `'screen'` or `'screen:modifier'`.

```typescript
imports: [ResponsivePipe];
```

```typescript
screen = inject(ResponsiveService).screen;
```

```html
<!-- Exact -->
@if (screen() | responsive:'mobile') { }

<!-- isAtLeast -->
@if (screen() | responsive:'tablet:least') { }

<!-- isAtMost -->
@if (screen() | responsive:'desktop:most') { }

<!-- isLarger -->
@if (screen() | responsive:'tablet:larger') { }

<!-- isSmaller -->
@if (screen() | responsive:'tablet:smaller') { }
```

| Modifier   | Equivalent service method |
| ---------- | ------------------------- |
| _(none)_   | `is()`                    |
| `:least`   | `isAtLeast()`             |
| `:most`    | `isAtMost()`              |
| `:larger`  | `isLarger()`              |
| `:smaller` | `isSmaller()`             |

---

## Custom Breakpoints

Call `provideResponsive()` in `app.config.ts` to override the default values. Only pass what you need — the rest keeps its default.

```typescript
// app.config.ts
import { provideResponsive } from '@irv-labs/ngx-responsive-signals';

export const appConfig: ApplicationConfig = {
  providers: [
    provideResponsive({
      mobile: 640,
      desktop: 1440,
      // tablet and largeTablet stay at their defaults
    }),
  ],
};
```

Calling `provideResponsive()` is optional — the service works with the default breakpoints out of the box.

---

## SSR

The service is SSR-safe. On the server, `window` is never accessed — the screen defaults to `Mobile` (width `0`), which is the most conservative fallback for layout decisions.

---

## Path Alias (optional)

If you prefer a shorter import, add a path alias in your `tsconfig.json`:

```json
{
  "compilerOptions": {
    "paths": {
      "@responsive": ["./node_modules/@irv-labs/ngx-responsive-signals"]
    }
  }
}
```

```typescript
// Before
import { ResponsiveService, Screen } from '@irv-labs/ngx-responsive-signals';

// After
import { ResponsiveService, Screen } from '@responsive';
```

This is a local convenience — it does not affect the published package or teammates unless they add the same alias.

---

## Public API

| Export                   | Description                                                           |
| ------------------------ | --------------------------------------------------------------------- |
| `ResponsiveService`      | Core service — `is`, `isAtLeast`, `isAtMost`, `isLarger`, `isSmaller` |
| `ResponsivePipe`         | Pipe that resolves a screen + modifier to `boolean`                   |
| `provideResponsive`      | Registers custom breakpoints in `app.config`                          |
| `Screen`                 | Enum — `Mobile`, `Tablet`, `LargeTablet`, `Desktop`, `LargeDesktop`   |
| `SCREEN_ORDER`           | Ordinal map used internally for comparisons                           |
| `RESPONSIVE_BREAKPOINTS` | `InjectionToken` for the breakpoints config                           |
| `DEFAULT_BREAKPOINTS`    | Default breakpoint values                                             |
| `ResponsiveBreakpoints`  | Config interface for `provideResponsive`                              |
| `ResponsiveModifier`     | `'least' \| 'most' \| 'larger' \| 'smaller'`                          |
| `ResponsivePipeArg`      | Union type for all valid pipe arguments                               |

---

## Philosophy

Angular Signals make reactive state simple — a boolean that updates when the window resizes should be nothing more than a `computed`. This library takes that idea to its logical conclusion: one service, a handful of methods, all returning signals. No Observables to subscribe to, no `async` pipes, no manual cleanup.

The comparison model is built on a single ordinal map — each screen maps to a number, and `isAtLeast`, `isAtMost`, `isLarger`, `isSmaller` become single-line comparisons. The result is predictable, testable, and easy to reason about.

- **Signal-native** — every method returns `Signal<boolean>`, composable with any `computed` or `effect`
- **Zero setup** — works without `provideResponsive()`, custom breakpoints are opt-in
- **SSR-safe** — no `window` access on the server
- **No memory leaks** — the resize listener is cleaned up via `DestroyRef`
- **Dev-time safety** — breakpoint order is validated at startup, not silently ignored

---

## License

MIT