npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

ngx-data-pulse-workspace

v2.2.0

Published

[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

Readme

🌠 NgxDataPulse 🌠

License: MIT

Boîte à outils Angular 18+ pour vous faciliter la vie dans tous types de projets.

⚠️ Package en cours de développement, les fonctionnalités sont susceptibles d'évoluer.

🔧 Installation et Configuration

Installation via NPM

npm i ngx-data-pulse

Initialisation de la bibliothèque

Pour utiliser ngx-data-pulse, vous devez initialiser la bibliothèque dans votre application Angular.

Dans un projet Angular standalone

Dans votre fichier main.ts :

import { bootstrapApplication } from "@angular/platform-browser";
import { appConfig } from "./app/app.config";
import { AppComponent } from "./app/app.component";
import { provideNgxDataPulse } from "ngx-data-pulse";

bootstrapApplication(AppComponent, {
  providers: [...appConfig.providers, provideNgxDataPulse()],
}).catch((err) => console.error(err));

💻 Compatibilité

  • Angular 18+
  • TypeScript 5.4+

🚀 Fonctionnalités

  • API : Service complet pour gérer les appels HTTP avec gestion de l'authentification et des erreurs.
  • Storage : Service de stockage local avancé avec gestion de l'expiration et du chiffrement.
  • Modal : Service de gestion des modals.
  • Notification : Service de notification personnalisable.
  • Events : Service de gestion d'événements global avec historique.
  • Date : Service de manipulation de dates.
  • Number : Service de manipulation de nombres.
  • Pipes : Pipes pour formater des données directement dans le template (date, number, currency, etc.).
  • Network : Service de vérification de la connexion réseau.
  • Idle : Service de détection de l'inactivité de l'utilisateur.
  • SEO : Service de gestion des métadonnées SEO.
  • Navigation : Service de gestion de la navigation.
  • Loader : Service de gestion des loaders personnalisés.
  • Scroller : Service de gestion du défilement.
  • Platform : Service de gestion et détection de la plateforme.

🚀 Service d'API

Utilisation Simple

import { api } from "ngx-data-pulse";

// Création d'un signal pour les appels API
const usersApi = api.get<User[]>();

// Dans votre composant
@Component({
  template: `
    <div *ngIf="usersApi.loading()">Chargement...</div>
    <div *ngIf="usersApi.error()">{{ usersApi.error()?.message }}</div>
    <ul *ngIf="usersApi.data()">
      <li *ngFor="let user of usersApi.data()">{{ user.name }}</li>
    </ul>
  `,
})
export class UsersComponent {
  constructor() {
    // Exécution de la requête
    this.usersApi.execute("/users");
  }
}

Exemple Concret : Profil Utilisateur

// Interface de notre modèle
interface User {
  id: number;
  name: string;
  email: string;
}

// Composant de profil utilisateur
@Component({
  selector: "app-user-profile",
  template: `
    <!-- Affichage du loader -->
    <div *ngIf="userApi.loading()">Chargement du profil...</div>

    <!-- Affichage des erreurs -->
    <div *ngIf="userApi.error()" class="error">
      {{ userApi.error()?.message }}
    </div>

    <!-- Affichage des données -->
    <div *ngIf="userApi.data()" class="profile">
      <h2>{{ userApi.data()?.name }}</h2>
      <p>Email: {{ userApi.data()?.email }}</p>
      <p>ID: {{ userApi.data()?.id }}</p>
    </div>
  `,
})
export class UserProfileComponent implements OnInit {
  // Création du signal API
  userApi = api.get<User>();

  async ngOnInit() {
    try {
      // Appel API pour récupérer l'utilisateur 123
      await this.userApi.execute("/users/123");
    } catch (error) {
      console.error("Erreur lors de la récupération du profil");
    }
  }
}

Méthodes HTTP disponibles

// GET
const getUsers = api.get<User[]>();
await getUsers.execute("/users");

// POST avec body typé
const createUser = api.post<User, CreateUserDto>();
await createUser.execute("/users", {
  name: "John",
  email: "[email protected]",
});

// PUT avec body typé
const updateUser = api.put<User, UpdateUserDto>();
await updateUser.execute("/users/123", {
  name: "Jane",
});

// PATCH avec body typé
const patchUser = api.patch<User, Partial<User>>();
await patchUser.execute("/users/123", {
  email: "[email protected]",
});

// DELETE
const deleteUser = api.delete<void>();
await deleteUser.execute("/users/123");

Gestion des types de réponse

// JSON (par défaut)
const jsonApi = api.get<JsonData>();
await jsonApi.execute("/api/data");

// Texte
const textApi = api.get<string>();
await textApi.execute("/api/text", {}, "text");

// Blob (fichiers)
const fileApi = api.get<Blob>();
await fileApi.execute("/api/file", {}, "blob");

// ArrayBuffer
const binaryApi = api.get<ArrayBuffer>();
await binaryApi.execute("/api/binary", {}, "arraybuffer");

Gestion de l'authentification

// Configuration du token
api.setToken("votre-jwt-token");

// Les appels incluront automatiquement le token
const privateApi = api.get<PrivateData>();
await privateApi.execute("/api/private");

// Déconnexion
api.clearToken();

Gestion des erreurs

const dataApi = api.get<Data>();

try {
  await dataApi.execute("/api/data");
} catch (error) {
  if (dataApi.error()) {
    console.error(`${dataApi.error()?.status}: ${dataApi.error()?.message}`);
  }
}

// Réinitialisation
dataApi.reset();

États disponibles

Chaque signal API fournit :

  • data() : Données de la réponse
  • loading() : État de chargement
  • error() : Erreur éventuelle
  • status() : Code HTTP
  • reset() : Réinitialisation
  • execute() : Exécution de la requête

🚀 Service de Stockage

Configuration

import { storage } from "ngx-data-pulse";

// Configuration optionnelle
storage.configure({
  prefix: "app_", // Préfixe pour les clés (défaut: 'ngx_')
  encryptionKey: "ma-clé-secrète", // Active le chiffrement
});

Stockage Simple

// Stockage basique
storage.put({
  key: "user",
  data: { id: 1, name: "John" },
});

// Avec durée de vie (TTL)
storage.put({
  key: "session",
  data: { token: "xyz" },
  ttl: 3600, // Expire dans 1 heure
});

// Avec date d'expiration
storage.put({
  key: "promo",
  data: { code: "SUMMER" },
  expiresAt: new Date("2024-12-31").getTime(),
});

Récupération

interface User {
  id: number;
  name: string;
}

// Récupération simple
const user = storage.get<User>("user");
if (user) {
  console.log(user.name); // 'John'
}

// Vérification d'existence
if (storage.has("session")) {
  // La clé existe et n'est pas expirée
}

// Récupération avec métadonnées
const item = storage.getItem<User>("user");
if (item) {
  console.log(item.data.name); // 'John'
  console.log(new Date(item.createdAt)); // Date de création
  console.log(new Date(item.updatedAt)); // Date de modification
}

Recherche et Filtrage

interface Product {
  id: number;
  name: string;
  price: number;
}

// Récupérer tous les produits
const products = storage.getAll<Product>();

// Recherche avec filtre
const cheapProducts = storage.search<Product>((product) => product.price < 10);

Mise à jour

// Mise à jour des données
storage.update("user", {
  id: 1,
  name: "John Doe", // Nouveau nom
});

// Prolonger la durée de vie
storage.touch("session", 1800); // +30 minutes

Suppression

// Supprimer une entrée
storage.delete("user");

// Supprimer toutes les entrées
storage.reset();

Exemple Complet

interface UserProfile {
  id: number;
  name: string;
  email: string;
  role: "user" | "admin";
}

// Configuration avec chiffrement
storage.configure({
  prefix: "myapp_",
  encryptionKey: "clé-très-secrète",
});

// Stockage d'un profil utilisateur
storage.put<UserProfile>({
  key: "profile",
  data: {
    id: 1,
    name: "John Doe",
    email: "[email protected]",
    role: "admin",
  },
  ttl: 86400, // Expire dans 24h
});

// Recherche d'administrateurs
const admins = storage.search<UserProfile>(
  (profile) => profile.role === "admin"
);

// Mise à jour du profil
if (storage.has("profile")) {
  const profile = storage.get<UserProfile>("profile");
  if (profile) {
    storage.update("profile", {
      ...profile,
      name: "John Smith",
    });
  }
}

// Nettoyage à la déconnexion
storage.reset();

🚀 Service de Notification

Le service de notification permet d'afficher des notifications personnalisables dans votre application.

Configuration

import { notif, NotificationComponent } from "ngx-data-pulse";

// Configuration globale (optionnelle)
notif.configure({
  position: "top-right", // top-left, top-center, top-right, bottom-left, bottom-center, bottom-right
  duration: 5000, // durée en ms
  maxWidth: "400px",
  gap: "10px",
  styles: {
    success: {
      background: "#4caf50",
      color: "#fff",
      boxShadow: "0 2px 4px rgba(0,0,0,0.2)",
    },
    // ... autres styles
  },
  icons: {
    success: "✓",
    error: "✕",
    warning: "⚠",
    info: "ℹ",
  },
});

Utilisation

import { notif,NotificationComponent } from "ngx-data-pulse";

@Component({
  ...
  imports: [NotificationComponent],
  template: `<ngx-notifications></ngx-notifications>`,
  ...
})
export class AppComponent {
  showNotification() {
    // Notifications prédéfinies
    notif.success("Opération réussie !");
    notif.error("Une erreur est survenue");
    notif.warning("Attention");
    notif.info("Information");

    // Notification personnalisée
    notif.show("Message", {
      type: "success",
      duration: 3000,
      icon: "🚀",
      style: {
        background: "#000",
        color: "#fff",
      },
      closable: true,
    });
  }
}

Exemple Concret : Formulaire de Contact

@Component({
  selector: "app-contact",
  template: `
    <form (ngSubmit)="onSubmit()">
      <input [(ngModel)]="email" name="email" type="email" />
      <button type="submit">Envoyer</button>
    </form>
  `,
})
export class ContactComponent {
  async onSubmit() {
    try {
      await this.sendEmail();
      notif.success("Email envoyé avec succès !");
    } catch (error) {
      notif.error("Erreur lors de l'envoi de l'email");
    }
  }
}

🚀 Service d'Événements

Le service d'événements permet une communication entre composants sans couplage direct, basée sur les Signals.

Configuration et Utilisation

import { events } from "ngx-data-pulse";

// Définition d'un type d'événement
interface UserEvent {
  id: number;
  name: string;
}

// Création d'un événement typé
const userEvent = events.create<UserEvent>({
  type: "USER_UPDATED",
  initialData: { id: 0, name: "" }, // Optionnel
  keepHistory: true, // Optionnel
  historySize: 5, // Optionnel
});

// Dans un composant émetteur
@Component({
  template: `<button (click)="updateUser()">Mettre à jour</button>`,
})
export class SenderComponent {
  updateUser() {
    userEvent.emit({
      id: 1,
      name: "John Doe",
    });
  }
}

// Dans un composant récepteur
@Component({
  template: `
    <div>Utilisateur : {{ userEvent.data()?.name }}</div>
    <div>Dernière mise à jour : {{ userEvent.lastEmitted() | date }}</div>
  `,
})
export class ReceiverComponent implements OnInit, OnDestroy {
  userEvent = events.create<UserEvent>({ type: "USER_UPDATED" });
  private unsubscribe: () => void;

  ngOnInit() {
    // S'abonner aux changements
    this.unsubscribe = this.userEvent.on((user) => {
      console.log("Utilisateur mis à jour:", user);
    });
  }

  ngOnDestroy() {
    // Se désabonner
    this.unsubscribe();
  }
}

### Gestion de l'Historique

L'historique des événements permet de suivre l'évolution des données dans le temps.

```typescript
// Configuration avec historique
const userEvent = events.create<UserEvent>({
  type: "USER_UPDATED",
  keepHistory: true, // Active l'historique
  historySize: 5, // Limite la taille de l'historique
  initialData: { id: 0, name: "" }, // Données initiales
});

// Composant avec historique
@Component({
  template: `
    <!-- Données actuelles -->
    <div class="current">
      Utilisateur actuel : {{ userEvent.data()?.name }}
      <small>Mis à jour le {{ userEvent.lastEmitted() | date:'short' }}</small>
    </div>

    <!-- Historique des modifications -->
    <div class="history">
      <h3>Historique ({{ userEvent.history().length }} modifications)</h3>

      <div class="timeline">
        @for (user of userEvent.history(); track user.id) {
          <div class="timeline-item">
            <div class="name">{{ user.name }}</div>
          </div>
        }
      </div>
    </div>
  `,
  styles: [`
    .timeline {
      border-left: 2px solid #ccc;
      padding-left: 1rem;
    }
    .timeline-item {
      margin: 1rem 0;
      padding: 0.5rem;
      border-radius: 4px;
      background: #f5f5f5;
    }
  `]
})
export class UserHistoryComponent {
  userEvent = events.create<UserEvent>({
    type: "USER_UPDATED",
    keepHistory: true,
    historySize: 5
  });

  // Exemple d'utilisation
  updateUser() {
    this.userEvent.emit({
      id: Date.now(),
      name: `John Doe ${new Date().toLocaleTimeString()}`
    });
  }

  // Accès programmatique à l'historique
  logHistory() {
    console.log("Données actuelles:", this.userEvent.data());
    console.log("Historique:", this.userEvent.history());
    console.log("Dernière mise à jour:", new Date(this.userEvent.lastEmitted() || 0));
  }
}

Fonctionnement de l'Historique

  • keepHistory: Active la conservation de l'historique
  • historySize: Nombre maximum d'entrées dans l'historique (par défaut: 10)
  • history(): Signal contenant le tableau des anciennes valeurs
  • lastEmitted(): Signal contenant le timestamp de la dernière émission

Exemple avec Plusieurs Composants

// Composant émetteur
@Component({
  template: `
    <div class="actions">
      <button (click)="updateName('John')">John</button>
      <button (click)="updateName('Jane')">Jane</button>
      <button (click)="updateName('Bob')">Bob</button>
    </div>
  `
})
export class UserEditorComponent {
  userEvent = events.create<UserEvent>({ type: "USER_UPDATED" });

  updateName(name: string) {
    this.userEvent.emit({
      id: Date.now(),
      name: name
    });
  }
}

// Composant d'historique
@Component({
  template: `
    <div class="history-widget">
      <h4>Modifications Récentes</h4>
      <ul class="changes">
        @for (user of userEvent.history().slice().reverse(); track user.id) {
          <li>
            <span class="name">{{ user.name }}</span>
            <span class="time">{{ getRelativeTime(user.id) }}</span>
          </li>
        }
      </ul>
    </div>
  `,
  styles: [`
    .history-widget {
      border: 1px solid #eee;
      padding: 1rem;
      border-radius: 8px;
    }
    .changes {
      list-style: none;
      padding: 0;
    }
    .changes li {
      display: flex;
      justify-content: space-between;
      padding: 0.5rem 0;
      border-bottom: 1px solid #eee;
    }
  `]
})
export class UserHistoryWidgetComponent {
  userEvent = events.create<UserEvent>({
    type: "USER_UPDATED",
    keepHistory: true,
    historySize: 5
  });

  getRelativeTime(timestamp: number): string {
    const diff = Date.now() - timestamp;
    if (diff < 60000) return "À l'instant";
    if (diff < 3600000) return `Il y a ${Math.floor(diff / 60000)} min`;
    return new Date(timestamp).toLocaleTimeString();
  }
}

### Nettoyage

```typescript
// Supprimer un événement spécifique
events.remove("USER_UPDATED");

// Supprimer tous les événements
events.clear();

🚀 Service de Dates

Le service de dates permet de manipuler facilement les dates en français.

import { date } from "ngx-data-pulse";

// Formatage de dates
date.format(new Date(), { format: "short" }); // "01/01/2024"
date.format(new Date(), { format: "medium" }); // "1 janvier 2024"
date.format(new Date(), { format: "long" }); // "1 janvier 2024 14:30"
date.format(new Date(), { format: "full" }); // "mardi 1 janvier 2024 14:30:00"

// Calcul de différences
date.diff("2024-01-01", "2024-02-01", { unit: "days" }); // 31
date.diff("2024-01-01", "2025-01-01", { unit: "months" }); // 12

// Temps relatif
date.fromNow("2024-01-01"); // "il y a 2 mois"
date.fromNow(date.add(new Date(), 1, "days")); // "dans 1 jour"

// Vérifications
date.isToday("2024-01-01"); // false
date.isFuture("2025-01-01"); // true
date.isPast("2023-01-01"); // true

// Manipulation
date.add("2024-01-01", 1, "months"); // 2024-02-01
date.subtract("2024-01-01", 1, "years"); // 2023-01-01

// Conversions de formats
date.frToIso("01/01/2024"); // "2024-01-01"
date.isoToFr("2024-01-01"); // "01/01/2024"
date.usToFr("01/31/2024"); // "31/01/2024"
date.frToUs("31/01/2024"); // "01/31/2024"

// Conversions avec timestamps
date.isoToTimestamp("2024-01-01"); // 1704067200000
date.timestampToIso(1704067200000); // "2024-01-01"
date.frToTimestamp("01/01/2024"); // 1704067200000
date.timestampToFr(1704067200000); // "01/01/2024"

// Conversions pour API
date.toApiDate("01/01/2024"); // "2024-01-01T00:00:00.000Z"
date.fromApiDate("2024-01-01T00:00:00.000Z"); // Date object

🔢 Service de Nombres

Le service de nombres permet de manipuler et formater facilement les nombres en français.

import { num } from "ngx-data-pulse";

// Formatage simple
num.format(1234.5678); // "1 234,57"
num.format(1234.5678, { decimals: 3 }); // "1 234,568"
num.format(1234, { forceDecimals: true }); // "1 234,00"

// Formatage monétaire
num.currency(1234.56); // "1 234,56 €"
num.currency(1234.56, { currency: "USD", symbolPosition: "before" }); // "$ 1 234,56"
num.currency(1234.56, { symbolSpace: false }); // "1 234,56€"

// Conversion de devises
num.convert(100, { from: "EUR", to: "USD", rate: 1.1 }); // 110

// Arrondis et troncature
num.round(1234.5678, 2); // 1234.57
num.truncate(1234.5678, 2); // 1234.56

// Parsing
num.parse("1 234,56"); // 1234.56
num.parse("1.234,56", ","); // 1234.56

// Pourcentages
num.percentage(25, 100); // 25
num.percentage(25, 100, 1); // 25.0

⭐Les Pipes NgX-Data-Pulse

Les pipes sont des composants Angular qui permettent de formater des données directement dans le template.

import {
  NumberPipe,
  CurrencyPipe,
  PercentPipe,
  DatePipe,
  FromNowPipe,
  ApiDatePipe,
} from "ngx-data-pulse";

@Component({
  standalone: true,
  imports: [
    NumberPipe,
    CurrencyPipe,
    PercentPipe,
    DatePipe,
    FromNowPipe,
    ApiDatePipe,
  ],
  template: `
    <!-- Formatage de nombres -->
    <div>{{ 1234.5678 | ngxNumber }}</div>
    <!-- "1 234,57" -->
    <div>{{ 1234.5678 | ngxNumber : { decimals: 3 } }}</div>
    <!-- "1 234,568" -->

    <!-- Formatage de devises -->
    <div>{{ 1234.56 | ngxCurrency }}</div>
    <!-- "1 234,56 €" -->
    <div>
      {{
        1234.56 | ngxCurrency : { currency: "USD", symbolPosition: "before" }
      }}
    </div>
    <!-- "$ 1 234,56" -->

    <!-- Pourcentages -->
    <div>{{ 25 | ngxPercent }}</div>
    <!-- "25 %" -->
    <div>{{ 25 | ngxPercent : 100 : 1 }}</div>
    <!-- "25,0 %" -->

    <!-- Formatage de dates -->
    <div>{{ date | ngxDate }}</div>
    <!-- "01/01/2024" -->
    <div>{{ date | ngxDate : { format: "full" } }}</div>
    <!-- "mardi 1 janvier 2024 14:30:00" -->

    <!-- Temps relatif -->
    <div>{{ date | ngxFromNow }}</div>
    <!-- "il y a 2 mois" -->

    <!-- Format API -->
    <div>{{ date | ngxApiDate }}</div>
    <!-- "2024-01-01T00:00:00.000Z" -->
  `,
})
export class AppComponent {
  date = new Date();
}

🌐 Service Réseau

Le service réseau permet de détecter l'état de la connexion et de réagir aux changements.

import { network } from "ngx-data-pulse";

// Configuration (optionnelle)
network.configure({
  checkInterval: 30000, // Intervalle de vérification (30s)
  testUrl: "https://api.example.com/health", // URL de test
  timeout: 5000, // Timeout des requêtes (5s)
});

// Dans un composant
@Component({
  template: `
    <div class="status" [class]="network.state().status">
      {{ network.state().status === "online" ? "Connecté" : "Hors ligne" }}
      @if (network.state().latency) {
      <small>{{ network.state().latency }}ms</small>
      }
    </div>
  `,
  styles: [
    `
      .status {
        padding: 0.5rem 1rem;
        border-radius: 4px;
        display: flex;
        align-items: center;
        gap: 0.5rem;
      }
      .online {
        background: #4caf50;
        color: white;
      }
      .offline {
        background: #f44336;
        color: white;
      }
    `,
  ],
})
export class NetworkStatusComponent {
  network = network;
}

// Vérification manuelle
const isOnline = await network.check();

// Écoute des changements avec les événements
const networkEvent = events.create<NetworkState>({ type: "NETWORK_STATUS" });
networkEvent.on((state) => {
  console.log(`Réseau ${state.status} - Latence: ${state.latency}ms`);
});

🕒 Service d'Inactivité

Le service d'inactivité permet de détecter l'inactivité de l'utilisateur et de déclencher des actions.

import { idle } from "ngx-data-pulse";

// Configuration simple
idle.configure({
  timeout: 900000, // Délai d'inactivité (15min)
  warningDelay: 60000, // Délai d'avertissement (1min)
  events: ["mousemove", "keydown", "click", "scroll", "touchstart"],
  autoLogout: true,
  showWarning: true,
});

// Configuration avec déconnexion personnalisée
idle.configure({
  timeout: 900000,
  autoLogout: true,
  onLogout: () => {
    // Exemple avec un service d'authentification
    authService.logout();

    // Ou avec un store NgRx/Redux
    store.dispatch(logout());

    // Ou avec le router Angular
    router.navigate(["/auth/logout"]);

    // Ou une combinaison d'actions
    localStorage.clear();
    sessionStorage.clear();
    window.location.href = "/connexion";
  },
});

// Dans un composant
@Component({
  template: `
    <div class="idle-status" [class]="getStatusClass()">
      @if (idle.state().isWarning) {
      <div class="warning">Déconnexion dans {{ getRemainingTime() }}s</div>
      } @if (idle.state().isIdle) {
      <div class="idle">Session expirée</div>
      }

      <div class="last-activity">
        Dernière activité : {{ getLastActivity() }}
      </div>
    </div>
  `,
  styles: [
    `
      .idle-status {
        padding: 1rem;
        border-radius: 4px;
        margin: 1rem 0;
      }
      .warning {
        color: #ff9800;
        font-weight: bold;
      }
      .idle {
        color: #f44336;
        font-weight: bold;
      }
      .last-activity {
        font-size: 0.9em;
        color: #666;
      }
    `,
  ],
})
export class IdleStatusComponent {
  idle = idle;

  getStatusClass(): string {
    if (this.idle.state().isIdle) return "idle";
    if (this.idle.state().isWarning) return "warning";
    return "active";
  }

  getRemainingTime(): number {
    return Math.round(this.idle.state().remainingTime / 1000);
  }

  getLastActivity(): string {
    return new Date(this.idle.state().lastActivity).toLocaleTimeString();
  }
}

// Écoute des changements avec les événements
const idleEvent = events.create<IdleState>({ type: "IDLE_STATUS" });
idleEvent.on((state) => {
  if (state.isWarning) {
    console.log(
      `Avertissement : déconnexion dans ${state.remainingTime / 1000}s`
    );
  }
  if (state.isIdle) {
    console.log("Session expirée");
  }
});

Configuration

| Option | Type | Défaut | Description | | -------------- | ------------ | ------------------- | ------------------------------------- | | timeout | number | 900000 | Délai d'inactivité en ms (15min) | | warningDelay | number | 60000 | Délai d'avertissement en ms (1min) | | events | string[] | ["mousemove",...] | Actions à surveiller | | autoLogout | boolean | true | Déconnexion automatique | | onLogout | () => void | undefined | Fonction de déconnexion personnalisée | | showWarning | boolean | true | Afficher un avertissement |

Actions personnalisées

Le service permet de définir des actions qui seront déclenchées après un délai d'inactivité spécifique :

idle.configure({
  timeout: 900000, // 15min
  actions: [
    {
      timeout: 120000, // 2min
      description: "Sauvegarde automatique",
      callback: () => saveCurrentWork(),
    },
    {
      timeout: 300000, // 5min
      description: "Mode économie d'énergie",
      callback: () => enablePowerSaveMode(),
    },
  ],
});

Chaque action est définie par :

  • timeout: délai en ms avant déclenchement
  • callback: fonction à exécuter
  • description: description optionnelle de l'action

🔍 Service SEO

Le service SEO permet de gérer dynamiquement les métadonnées pour le référencement.

import { seo } from "ngx-data-pulse";

// Configuration globale
seo.configure({
  defaultTitle: "Mon Site",
  titleSeparator: " | ",
  defaultDescription: "Description par défaut du site",
  defaultImage: "https://monsite.com/image.jpg",
  defaultLang: "fr",
});

// Dans un composant
@Component({
  template: `<h1>{{ title }}</h1>`,
})
export class ArticleComponent implements OnInit {
  title = "Mon Article";

  ngOnInit() {
    // Mise à jour des métadonnées
    seo.update({
      title: this.title,
      description: "Description détaillée de l'article",
      image: "https://monsite.com/article.jpg",
      type: "article",
      keywords: ["article", "blog", "actualités"],
      canonical: "https://monsite.com/article",
      meta: {
        author: "John Doe",
        "article:published_time": "2024-01-01",
      },
      og: {
        site_name: "Mon Blog",
        locale: "fr_FR",
      },
      twitter: {
        card: "summary_large_image",
        creator: "@johndoe",
      },
    });
  }
}

Résultat HTML

<html lang="fr">
  <head>
    <title>Mon Article | Mon Site</title>
    <meta name="description" content="Description détaillée de l'article" />
    <meta name="keywords" content="article, blog, actualités" />
    <meta name="author" content="John Doe" />
    <meta name="article:published_time" content="2024-01-01" />

    <link rel="canonical" href="https://monsite.com/article" />

    <!-- Open Graph -->
    <meta property="og:title" content="Mon Article" />
    <meta
      property="og:description"
      content="Description détaillée de l'article"
    />
    <meta property="og:image" content="https://monsite.com/article.jpg" />
    <meta property="og:type" content="article" />
    <meta property="og:site_name" content="Mon Blog" />
    <meta property="og:locale" content="fr_FR" />

    <!-- Twitter Card -->
    <meta name="twitter:card" content="summary_large_image" />
    <meta name="twitter:title" content="Mon Article" />
    <meta
      name="twitter:description"
      content="Description détaillée de l'article"
    />
    <meta name="twitter:image" content="https://monsite.com/article.jpg" />
    <meta name="twitter:creator" content="@johndoe" />
  </head>
  <body>
    <h1>Mon Article</h1>
  </body>
</html>

🎯 Service Modal

Le service modal permet d'afficher des fenêtres modales personnalisables.

import { modal,ModalComponent } from "ngx-data-pulse";

// Configuration globale
modal.configure({
  defaultStyles: true,
  closeOnOverlay: true,
  closeOnEscape: true,
  buttons: {
    close: "Fermer",
    confirm: "Valider",
    cancel: "Annuler",
  },
});

// Dans un composant
@Component({
  ...
  imports: [ModalComponent],
  template: `
    <ngx-modal></ngx-modal>
    <button (click)="showModal()">Ouvrir</button>
  `,
})
export class AppComponent {
  showModal() {
    // Modal d'information
    modal.info("Votre message ici", {
      title: "Information",
      classes: {
        modal: "custom-modal",
        header: "custom-header",
      },
    });

    // Modal de confirmation
    modal.confirm("Êtes-vous sûr ?", {
      onConfirm: () => console.log("Confirmé"),
      onCancel: () => console.log("Annulé"),
    });

    // Modal d'erreur
    modal.error("Une erreur est survenue", {
      closeOnOverlay: false,
    });

    // Modal d'alerte
    modal.alert("Attention !", {
      showClose: false,
    });

    // Modal personnalisée
    modal.open({
      type: "info",
      title: "Titre",
      content: "<h3>Contenu HTML</h3><p>Votre contenu ici...</p>",
      classes: {
        container: "modal-container",
        overlay: "modal-overlay",
        modal: "modal-content",
        header: "modal-header",
        body: "modal-body",
        footer: "modal-footer",
        closeButton: "modal-close",
        actionButton: "modal-button",
      },
      onOpen: () => console.log("Modal ouverte"),
      onClose: () => console.log("Modal fermée"),
    });
  }
}

Types de Modales

  • info: Modal d'information simple
  • alert: Modal d'alerte (sans fermeture par overlay)
  • error: Modal d'erreur (sans fermeture par overlay)
  • confirm: Modal de confirmation avec boutons Confirmer/Annuler

Personnalisation

Chaque modal peut être personnalisée avec :

  • Classes CSS personnalisées
  • Callbacks (onOpen, onClose, onConfirm, onCancel)
  • Textes des boutons
  • Animations d'entrée/sortie
  • Comportement (fermeture par overlay/escape)
  • Styles par défaut activables/désactivables

Configuration des classes CSS globales

Vous pouvez définir des classes CSS globales pour chaque type de modal lors de la configuration du service :

modalService.configure({
  classes: {
    default: {
      container: "modal-container",
      overlay: "modal-overlay",
      modal: "modal-base",
      header: "modal-header",
      body: "modal-body",
      footer: "modal-footer",
    },
    info: {
      modal: "modal-info",
      actionButton: "btn-info",
    },
    error: {
      modal: "modal-error",
      actionButton: "btn-error",
    },
    alert: {
      modal: "modal-alert",
      actionButton: "btn-warning",
    },
    confirm: {
      modal: "modal-confirm",
      actionButton: "btn-primary",
    },
  },
});

Les classes définies dans default s'appliquent à tous les types de modals. Les classes spécifiques à chaque type (info, error, alert, confirm) héritent et peuvent surcharger les classes par défaut.

Vous pouvez toujours surcharger ces classes globales lors de l'ouverture d'une modal spécifique :

modalService.info("Message", {
  classes: {
    modal: "custom-modal",
    actionButton: "custom-button",
  },
});

🧭 Service de Navigation

Le service de navigation permet de gérer la navigation de manière centralisée avec :

  • Historique de navigation
  • Redirection sécurisée après login/logout
  • Gardes de navigation
import { navigation } from "ngx-data-pulse";

// Configuration globale
navigation.configure({
  afterLoginUrl: "/dashboard",
  afterLogoutUrl: "/login",
  maxHistorySize: 50,
  guards: [
    {
      canNavigate: () => !hasUnsavedChanges(),
      message:
        "Vous avez des modifications non sauvegardées. Voulez-vous quitter ?",
    },
  ],
});

// Navigation simple
await navigation.navigate("/users");

// Navigation avec garde spécifique
await navigation.navigate("/settings", {
  guards: [
    {
      canNavigate: () => isAdmin(),
      message: "Accès réservé aux administrateurs",
    },
  ],
});

// Navigation forcée (ignore les gardes)
await navigation.navigate("/logout", { force: true });

// Historique
navigation.back();
navigation.forward();

// Redirection après login
navigation.saveRedirectUrl();
await navigation.redirectAfterLogin();

// État de la navigation
const state = navigation.state();
console.log(state.currentUrl);
console.log(state.previousUrl);
console.log(state.history);

// Exemple dans un composant formulaire
@Component({
  template: `
    <form (ngSubmit)="onSubmit()">
      <input [(ngModel)]="data" name="data" />
      <button type="submit">Enregistrer</button>
    </form>
  `,
})
export class FormComponent implements OnInit, OnDestroy {
  private hasChanges = false;
  private guard: NavigationGuard = {
    canNavigate: () => !this.hasChanges,
    message: "Formulaire non sauvegardé. Continuer ?",
  };

  ngOnInit() {
    navigation.addGuard(this.guard);
  }

  ngOnDestroy() {
    navigation.removeGuard(this.guard);
  }

  onSubmit() {
    this.hasChanges = false;
    navigation.navigate("/success");
  }
}

Configuration

| Option | Type | Défaut | Description | | ---------------- | ------------------- | -------------- | ------------------------------- | | afterLoginUrl | string | "/dashboard" | URL de redirection après login | | afterLogoutUrl | string | "/login" | URL de redirection après logout | | maxHistorySize | number | 50 | Taille max de l'historique | | guards | NavigationGuard[] | [] | Gardes de navigation globaux |

Options de Navigation

| Option | Type | Défaut | Description | | --------- | ------------------- | ----------- | ----------------------- | | guards | NavigationGuard[] | undefined | Gardes spécifiques | | force | boolean | false | Ignore les gardes | | replace | boolean | false | Remplace l'URL actuelle |

📝 Service de Logs

Service de logs centralisé pour le debug et la surveillance.

import { logs } from "ngx-data-pulse";

// Configuration
logs.configure({
  enabled: true,
  minLevel: "warn",
  externalServiceUrl: "https://api.sentry.io/v1",
  apiKey: "votre-clé-api",
  environment: "production",
  tags: { version: "1.0.0" },
});

// Utilisation
logs.debug("Message de debug", { data: "optionnelle" });
logs.info("Information importante");
logs.warn("Attention !", { détails: "..." });
logs.error("Erreur critique", new Error("détails"));

// Accès à l'historique (signal)
const history = logs.history();

Configuration

| Option | Type | Défaut | Description | | ------------------ | -------------------------------------- | ------------- | ------------------------------------------- | | enabled | boolean | true | Active/désactive les logs | | minLevel | "debug" | "info" | "warn" | "error" | "debug" | Niveau minimum des logs | | externalServiceUrl | string | - | URL du service externe (Sentry, Datadog...) | | apiKey | string | - | Clé d'API du service externe | | environment | string | "development" | Environnement (dev, prod...) | | tags | Record<string, string> | - | Tags additionnels |

Fonctionnalités

  • 🎯 4 niveaux de logs : debug, info, warn, error
  • 📊 Historique complet accessible via signal
  • 🔄 Envoi automatique des erreurs vers Sentry/Datadog
  • 🏷️ Support des tags et métadonnées
  • 🕒 Horodatage automatique
  • 🎨 Formatage console avec couleurs
  • 🔍 Filtrage par niveau minimum
  • 💾 Conservation de l'historique

Structure d'une entrée

interface LogEntry {
  level: "debug" | "info" | "warn" | "error";
  message: string;
  data?: unknown;
  timestamp: number;
  tags?: Record<string, string>;
}

🖥️ Service de Plateforme

Service de détection de plateforme pour adapter l'interface utilisateur.

import { platform } from "ngx-data-pulse";

// Configuration
platform.configure({
  mobile: 768, // Breakpoint mobile en px
  tablet: 1024, // Breakpoint tablette en px
  autoUpdate: true, // Mise à jour auto sur resize
  debounceDelay: 250, // Délai de debounce en ms
});

// Utilisation
const info = platform.info(); // Signal avec les infos
console.log(info.type); // mobile, tablet, desktop
console.log(info.os); // ios, android, windows...
console.log(info.browser); // chrome, firefox, safari...

// Méthodes utilitaires
if (platform.isMobile()) {
  // Logique mobile
}

if (platform.isIOS()) {
  // Logique iOS
}

if (platform.isTouchEnabled()) {
  // Support tactile
}

Configuration

| Option | Type | Défaut | Description | | --------------- | --------- | ------ | ------------------- | | mobile | number | 768 | Breakpoint mobile | | tablet | number | 1024 | Breakpoint tablette | | autoUpdate | boolean | true | Mise à jour auto | | debounceDelay | number | 250 | Délai de debounce |

Informations disponibles

| Propriété | Type | Description | | ---------------- | ------------- | ---------------------- | | type | DeviceType | Type d'appareil | | os | OSType | Système d'exploitation | | browser | BrowserType | Navigateur utilisé | | browserVersion | string | Version du navigateur | | screenWidth | number | Largeur de l'écran | | screenHeight | number | Hauteur de l'écran | | orientation | string | Portrait ou paysage | | touchEnabled | boolean | Support tactile | | pixelRatio | number | Densité de pixels |

🔄 Service de Loader

Service de chargement personnalisable avec différents types d'animations.

import { loader } from "ngx-data-pulse";

// Configuration globale
loader.configure({
  type: "spinner",
  mode: "fullscreen",
  position: "center",
  delay: 200,
  minDuration: 500,
  overlay: true,
  overlayOpacity: 0.5,
  style: {
    colors: {
      primary: "#2196f3",
      secondary: "#bbdefb",
    },
    size: {
      width: "48px",
      height: "48px",
      thickness: "4px",
    },
  },
  animation: {
    name: "rotate",
    duration: 1000,
    timing: "linear",
    iterations: Infinity,
  },
});

// Dans un composant
@Component({
  template: `
    <ngx-loader></ngx-loader>
    <button (click)="showLoader()">Charger</button>
  `,
})
export class AppComponent {
  showLoader() {
    // Loader simple
    const id = loader.show();

    // Loader avec texte
    loader.show({
      text: "Chargement en cours...",
    });

    // Loader avec barre de progression
    loader.show({
      type: "progress",
      mode: "block",
      text: "Téléchargement...",
    });

    // Loader avec points
    loader.show({
      type: "dots",
      mode: "inline",
      animation: {
        name: "bounce",
        duration: 500,
      },
    });

    // Loader avec pulse
    loader.show({
      type: "pulse",
      style: {
        colors: {
          primary: "#4caf50",
        },
      },
    });

    // Loader personnalisé
    loader.show({
      type: "custom",
      template: "<div class='custom-loader'>...</div>",
    });

    // Cacher un loader
    loader.hide(id);

    // Cacher tous les loaders
    loader.hideAll();
  }
}

Types de Loaders

| Type | Description | | -------- | -------------------------- | | spinner | Spinner rotatif | | progress | Barre de progression | | dots | Points animés | | pulse | Cercle pulsant | | custom | Template HTML personnalisé |

Modes d'affichage

| Mode | Description | | ---------- | -------------------------- | | fullscreen | Plein écran avec overlay | | block | Bloc avec position absolue | | inline | En ligne avec le contenu |

Positions

| Position | Description | | -------- | --------------- | | center | Centré (défaut) | | top | En haut | | bottom | En bas | | left | À gauche | | right | À droite |

Animations disponibles

| Animation | Description | | --------- | ----------------------- | | rotate | Rotation continue | | progress | Translation horizontale | | bounce | Rebond vertical | | pulse | Pulsation avec opacité |

Styles personnalisables

interface LoaderStyle {
  classes?: {
    container?: string;
    overlay?: string;
    loader?: string;
    text?: string;
  };
  colors?: {
    primary?: string;
    secondary?: string;
    background?: string;
    text?: string;
  };
  size?: {
    width?: string;
    height?: string;
    thickness?: string;
  };
}

Exemple avec API

@Component({
  template: `
    <div class="users" [class.loading]="loading">
      <ngx-loader></ngx-loader>
      <button (click)="loadUsers()">Charger</button>
      <ul>
        @for (user of users; track user.id) {
        <li>{{ user.name }}</li>
        }
      </ul>
    </div>
  `,
  styles: [
    `
      .users {
        position: relative;
        min-height: 200px;
      }
      .loading {
        opacity: 0.7;
        pointer-events: none;
      }
    `,
  ],
})
export class UsersComponent {
  async loadUsers() {
    const loaderId = loader.show({
      mode: "block",
      text: "Chargement des utilisateurs...",
      minDuration: 500,
    });

    try {
      const response = await fetch("/api/users");
      const users = await response.json();
      this.users = users;
    } finally {
      loader.hide(loaderId);
    }
  }
}

🌀 Service de Scroll

Service de gestion du scroll et des animations avec détection de position.

import { scroller } from "ngx-data-pulse";

// Configuration globale
scroller.configure({
  topThreshold: 100,
  bottomThreshold: 100,
  behavior: "smooth",
  debounceDelay: 100,
  offset: { top: 0, left: 0 },
});

// Scroll vers un élément
scroller.scrollTo("#section-1", {
  offsetTop: -60, // Offset pour le header
  behavior: "smooth",
});

// Verrouillage du scroll (ex: modal)
scroller.lock();
scroller.unlock();

// Animation au scroll
scroller.animate(element, {
  effect: "fade",
  threshold: 0.5,
  duration: 500,
  timing: "ease",
  delay: 0,
  once: true,
});

// Surveillance de l'état
const scrollState = scroller.state();
console.log(scrollState.position); // "top" | "middle" | "bottom"
console.log(scrollState.direction); // "up" | "down" | "none"
console.log(scrollState.progress); // 0-100

Effets d'animation disponibles

| Effet | Description | | ------------- | ------------------------- | | fade | Fondu à l'apparition | | slide-up | Glissement vers le haut | | slide-down | Glissement vers le bas | | slide-left | Glissement vers la gauche | | slide-right | Glissement vers la droite | | zoom | Effet de zoom |

Configuration

| Option | Type | Défaut | Description | | ----------------- | ----------------- | --------------------- | --------------------------- | | topThreshold | number | 100 | Seuil pour détecter le haut | | bottomThreshold | number | 100 | Seuil pour détecter le bas | | behavior | ScrollBehavior | "smooth" | Comportement du scroll | | debounceDelay | number | 100 | Délai de debounce | | offset | { top?, left? } | { top: 0, left: 0 } | Offset de scroll |

Exemple avec animations

import { Component } from "@angular/core";
import { scroller } from "ngx-data-pulse";

@Component({
  selector: "app-home",
  template: `
    <section class="hero">
      <h1 #title>Titre</h1>
      <p #description>Description</p>
      <button (click)="scrollToContent()">Voir plus</button>
    </section>

    <section #content class="content">
      <div #card1 class="card">...</div>
      <div #card2 class="card">...</div>
      <div #card3 class="card">...</div>
    </section>
  `,
})
export class HomeComponent {
  ngAfterViewInit() {
    // Animation du titre
    scroller.animate(this.title.nativeElement, {
      effect: "fade",
      duration: 800,
    });

    // Animation de la description
    scroller.animate(this.description.nativeElement, {
      effect: "slide-up",
      delay: 200,
    });

    // Animation des cartes
    [this.card1, this.card2, this.card3].forEach((card, i) => {
      scroller.animate(card.nativeElement, {
        effect: "slide-up",
        threshold: 0.3,
        delay: i * 200,
      });
    });
  }

  scrollToContent() {
    scroller.scrollTo(this.content.nativeElement, {
      offsetTop: -60,
    });
  }
}