ngx-signal-plus 2.4.0 → 2.4.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,23 +1,14 @@
1
1
  # ngx-signal-plus
2
2
 
3
- A powerful utility library that enhances Angular Signals with additional features for robust state management.
3
+ [![Angular 16-21](https://img.shields.io/badge/Angular-16--21-dd0031)](https://angular.dev/)
4
+ [![npm version](https://img.shields.io/npm/v/ngx-signal-plus.svg)](https://www.npmjs.com/package/ngx-signal-plus)
5
+ ![Coverage](https://img.shields.io/badge/coverage-89.14%25-brightgreen)
4
6
 
5
- ## Features
7
+ Bring validation, persistence, undo/redo, and reactive queries to Angular Signals on Angular 16+.
6
8
 
7
- - Enhanced signal operations with built-in state tracking
8
- - Type-safe validations and transformations
9
- - Persistent storage with automatic serialization
10
- - Time-based operations (debounce, throttle, delay)
11
- - Signal operators for transformation and combination
12
- - Built-in undo/redo functionality
13
- - Form handling with validation
14
- - Form groups with aggregated state and validation
15
- - Async state management with loading, error, and retry logic
16
- - Reactive Queries for server state (TanStack Query style)
17
- - Collection management with ID-based CRUD operations
18
- - Automatic cleanup and memory management
19
- - Performance optimizations
20
- - Transactions and batching for atomic operations
9
+ - Interactive playground: https://stackblitz.com/github/milad-hub/ngx-signal-plus
10
+ - Full API docs: https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/docs/API.md
11
+ - Repository README (contributors): https://github.com/milad-hub/ngx-signal-plus/blob/main/README.md
21
12
 
22
13
  ## Installation
23
14
 
@@ -27,643 +18,96 @@ npm install ngx-signal-plus
27
18
 
28
19
  ## Requirements
29
20
 
30
- - Angular >= 16.0.0 (fully compatible with Angular 16-20)
31
- - TypeScript >= 5.0.0
21
+ - Angular `>=16.0.0 <=21.0.0`
22
+ - TypeScript `>=5.0.0`
32
23
 
33
- ## Basic Usage
24
+ ## Why this library?
25
+
26
+ | Capability | Angular native | ngx-signal-plus |
27
+ | --- | --- | --- |
28
+ | Signal validation and validation helpers | Limited | `sp().validate()`, presets, schema helpers |
29
+ | localStorage persistence | Manual | `sp().persist()` |
30
+ | Undo/redo history | Manual | `sp().withHistory()` |
31
+ | Transaction rollback | Manual | `spTransaction()` |
32
+ | Middleware/interceptors | No built-in | `spUseMiddleware()` |
33
+ | Query cache/retry/invalidation | `resource/httpResource` (basic) | `spQuery()`, `spMutation()`, `QueryClient` |
34
+ | Collection CRUD helpers | Manual | `spCollection()` |
35
+
36
+ ## Quick Start
34
37
 
35
38
  ```typescript
36
- import { Component } from "@angular/core";
37
- import { sp, enhance, spMap, spFilter } from "ngx-signal-plus";
38
- import { signal, computed } from "@angular/core";
39
+ import { Component, computed } from "@angular/core";
40
+ import { sp } from "ngx-signal-plus";
39
41
 
40
42
  @Component({
41
43
  standalone: true,
42
44
  selector: "app-counter",
43
45
  template: `
44
- <div>Count: {{ counter.value() }}</div>
45
- <div>Doubled: {{ doubled() }}</div>
46
- <button (click)="increment()">Increment</button>
47
- <button (click)="decrement()">Decrement</button>
48
-
49
- @if (counter.history().length > 0) {
46
+ <p>Count: {{ counter.value }}</p>
47
+ <p>Doubled: {{ doubled() }}</p>
48
+ <button (click)="inc()">+</button>
49
+ <button (click)="dec()">-</button>
50
+ @if (counter.history().length > 1) {
50
51
  <button (click)="counter.undo()">Undo</button>
51
52
  }
52
53
  `,
53
54
  })
54
55
  export class CounterComponent {
55
- // Create an enhanced signal with persistence and history
56
- counter = sp(0)
57
- .persist("counter")
58
- .withHistory(10)
59
- .validate((value) => value >= 0)
60
- .build();
61
-
62
- // Use signal operators
63
- doubled = computed(() => this.counter.value() * 2);
56
+ counter = sp(0).persist("counter").withHistory(10).validate((n) => n >= 0).build();
57
+ doubled = computed(() => this.counter.value * 2);
64
58
 
65
- increment() {
66
- this.counter.setValue(this.counter.value() + 1);
59
+ inc() {
60
+ this.counter.setValue(this.counter.value + 1);
67
61
  }
68
62
 
69
- decrement() {
70
- if (this.counter.value() > 0) {
71
- this.counter.setValue(this.counter.value() - 1);
72
- }
63
+ dec() {
64
+ if (this.counter.value > 0) this.counter.setValue(this.counter.value - 1);
73
65
  }
74
66
  }
75
67
  ```
76
68
 
77
- ## Core Features
78
-
79
- ### Signal Creation
80
-
81
- ```typescript
82
- import { sp, spCounter, spToggle, spForm } from "ngx-signal-plus";
83
-
84
- // Simple enhanced signal
85
- const name = sp("John").build();
86
-
87
- // Counter with min/max validation
88
- const counter = spCounter(0, { min: 0, max: 100 });
89
-
90
- // Toggle (boolean) with persistence
91
- const darkMode = spToggle(false, "theme-mode");
92
-
93
- // Form input with validation
94
- const username = spForm.text("", {
95
- minLength: 3,
96
- maxLength: 20,
97
- debounce: 300,
98
- });
99
- ```
100
-
101
- ### Signal Enhancement
102
-
103
- Enhance existing signals with additional features:
104
-
105
- ```typescript
106
- import { enhance } from "ngx-signal-plus";
107
- import { signal } from "@angular/core";
108
-
109
- const enhanced = enhance(signal(0))
110
- .persist("counter")
111
- .validate((n) => n >= 0)
112
- .transform(Math.round)
113
- .withHistory(5)
114
- .debounce(300)
115
- .distinctUntilChanged()
116
- .build();
117
- ```
118
-
119
- ### Computed Signal Enhancement
120
-
121
- Create computed signals with persistence, history, and validation:
122
-
123
- ```typescript
124
- import { spComputed } from "ngx-signal-plus";
125
- import { signal } from "@angular/core";
126
-
127
- const firstName = signal("John");
128
- const lastName = signal("Doe");
129
-
130
- // Computed signal with history and persistence
131
- const fullName = spComputed(() => `${firstName()} ${lastName()}`, { persist: "user-fullname", historySize: 5 });
132
-
133
- fullName.value; // 'John Doe'
134
- firstName.set("Jane");
135
- fullName.value; // 'Jane Doe' (auto-updates)
136
- fullName.undo(); // 'John Doe'
137
- fullName.isValid(); // true
138
- ```
139
-
140
- ### Signal Operators
141
-
142
- ```typescript
143
- import { spMap, spFilter, spDebounceTime, spCombineLatest } from "ngx-signal-plus";
144
- import { signal } from "@angular/core";
145
-
146
- // Transform values
147
- const price = signal(100);
148
- const withTax = price.pipe(
149
- spMap((n) => n * 1.2),
150
- spMap((n) => Math.round(n * 100) / 100),
151
- );
152
-
153
- // Combine signals
154
- const firstName = signal("John");
155
- const lastName = signal("Doe");
156
- const fullName = spCombineLatest([firstName, lastName]).pipe(spMap(([first, last]) => `${first} ${last}`));
157
- ```
158
-
159
- ### Form Handling
160
-
161
- ```typescript
162
- import { spForm } from "ngx-signal-plus";
163
- import { computed } from "@angular/core";
164
-
165
- // Form inputs with validation
166
- const username = spForm.text("", { minLength: 3, maxLength: 20 });
167
- const email = spForm.email("");
168
- const age = spForm.number({ min: 18, max: 99, initial: 30 });
169
-
170
- // Form validation
171
- const isFormValid = computed(() => username.isValid() && email.isValid() && age.isValid());
172
- ```
173
-
174
- ### Form Groups
175
-
176
- Group multiple form controls together with aggregated state, validation, and persistence:
177
-
178
- ```typescript
179
- import { spFormGroup, spForm } from "ngx-signal-plus";
180
-
181
- // Basic form group
182
- const loginForm = spFormGroup({
183
- email: spForm.email(""),
184
- password: spForm.text("", { minLength: 8 }),
185
- });
186
-
187
- // Access aggregated state
188
- loginForm.isValid(); // false if password < 8 chars
189
- loginForm.isDirty(); // true if any field changed
190
- loginForm.isTouched(); // true if any field touched
191
- loginForm.value(); // { email: '', password: '' }
192
- loginForm.errors(); // { email: [...], password: [...] }
193
-
194
- // Update values
195
- loginForm.setValue({ email: "user@example.com", password: "secret123" });
196
- loginForm.patchValue({ email: "new@example.com" }); // Partial update
197
-
198
- // Form actions
199
- loginForm.reset(); // Reset all fields to initial values
200
- loginForm.markAsTouched(); // Mark all fields as touched
201
- loginForm.submit(); // Returns values if valid, null otherwise
202
-
203
- // Nested form groups
204
- const credentials = spFormGroup({
205
- email: spForm.email(""),
206
- password: spForm.text("", { minLength: 8 }),
207
- });
208
-
209
- const profile = spFormGroup({
210
- name: spForm.text(""),
211
- age: spForm.number({ min: 18 }),
212
- });
213
-
214
- const registrationForm = spFormGroup({
215
- credentials,
216
- profile,
217
- });
218
-
219
- // Group-level validation
220
- const passwordForm = spFormGroup(
221
- {
222
- password: spForm.text("password123"),
223
- confirmPassword: spForm.text("password123"),
224
- },
225
- {
226
- validators: [(values) => values.password === values.confirmPassword || "Passwords must match"],
227
- },
228
- );
229
-
230
- // Persistence
231
- const persistedForm = spFormGroup(
232
- {
233
- email: spForm.email(""),
234
- preferences: spForm.text(""),
235
- },
236
- {
237
- persistKey: "user-form", // Automatically saves/restores from localStorage
238
- },
239
- );
240
- ```
241
-
242
- ### Async State Management
243
-
244
- Manage asynchronous operations with built-in loading, error, and data states:
245
-
246
- ```typescript
247
- import { spAsync } from "ngx-signal-plus";
248
-
249
- const userData = spAsync<User>({
250
- fetcher: () => fetch("/api/user").then((r) => r.json()),
251
- initialValue: null,
252
- retryCount: 3,
253
- retryDelay: 1000,
254
- cacheTime: 5000,
255
- autoFetch: true,
256
- onSuccess: (data) => console.log("Loaded:", data),
257
- onError: (error) => console.error("Failed:", error),
258
- });
259
-
260
- // Reactive state signals
261
- userData.data(); // Signal<User | null>
262
- userData.loading(); // Signal<boolean>
263
- userData.error(); // Signal<Error | null>
264
- userData.isSuccess(); // Signal<boolean>
265
- userData.isError(); // Signal<boolean>
266
-
267
- // Methods
268
- await userData.refetch(); // Manually refetch data
269
- userData.invalidate(); // Mark cache as stale
270
- userData.reset(); // Reset to initial state
271
- userData.mutate(newData); // Optimistic update
272
- ```
273
-
274
- ### Reactive Queries
275
-
276
- ```typescript
277
- import { QueryClient, setGlobalQueryClient } from "ngx-signal-plus";
278
- import { spQuery, spMutation } from "ngx-signal-plus";
279
-
280
- const qc = new QueryClient();
281
- setGlobalQueryClient(qc);
282
-
283
- const todosQuery = spQuery({
284
- queryKey: ["todos"],
285
- queryFn: async () => fetch("/api/todos").then((r) => r.json()),
286
- staleTime: 5000,
287
- refetchOnWindowFocus: true,
288
- });
289
-
290
- const addTodo = spMutation({
291
- mutationFn: async (title: string) => postTodo(title),
292
- onMutate: (title) => {
293
- qc.setQueryData(["todos"], (prev) => [...((prev as { title: string }[] | undefined) ?? []), { title }], true);
294
- },
295
- onSuccess: () => qc.refetchQueries(["todos"]),
296
- });
297
- ```
298
-
299
- Highlights:
300
-
301
- - Cache-aware queries with invalidation and refetch
302
- - Mutations with optimistic updates
303
- - Interval/focus/reconnect refetch strategies
304
-
305
- ### Collection Management
306
-
307
- Manage arrays of entities with ID-based operations, optimized updates, and history support:
308
-
309
- ```typescript
310
- import { spCollection } from "ngx-signal-plus";
311
-
312
- interface Todo {
313
- id: string;
314
- title: string;
315
- completed: boolean;
316
- }
317
-
318
- const todos = spCollection<Todo>({
319
- idField: "id",
320
- initialValue: [],
321
- persist: "todos-key",
322
- withHistory: true,
323
- });
324
-
325
- // CRUD operations
326
- todos.add({ id: "1", title: "Learn Angular", completed: false });
327
- todos.addMany([todo1, todo2, todo3]);
328
- todos.update("1", { completed: true });
329
- todos.updateMany([
330
- { id: "1", changes: { completed: true } },
331
- { id: "2", changes: { title: "Updated" } },
332
- ]);
333
- todos.remove("1");
334
- todos.removeMany(["1", "2"]);
335
- todos.clear();
336
-
337
- // Query operations
338
- const todo = todos.findById("1");
339
- const completed = todos.filter((t) => t.completed);
340
- const firstCompleted = todos.find((t) => t.completed);
341
- const hasCompleted = todos.some((t) => t.completed);
342
- const allCompleted = todos.every((t) => t.completed);
343
-
344
- // Transform operations
345
- const sorted = todos.sort((a, b) => a.title.localeCompare(b.title));
346
- const titles = todos.map((t) => t.title);
347
- const totalCompleted = todos.reduce((acc, t) => acc + (t.completed ? 1 : 0), 0);
348
-
349
- // History operations
350
- todos.undo(); // Undo last operation
351
- todos.redo(); // Redo last undone operation
352
- todos.canUndo(); // Check if undo is available
353
- todos.canRedo(); // Check if redo is available
354
-
355
- // Reactive signals
356
- todos.value(); // Signal<Todo[]>
357
- todos.count(); // Signal<number>
358
- todos.isEmpty(); // Signal<boolean>
359
- ```
360
-
361
- ### Validation and Presets
362
-
363
- ```typescript
364
- import { spValidators, spPresets } from "ngx-signal-plus";
365
-
366
- // Use built-in validators
367
- const email = sp("").validate(spValidators.string.required).validate(spValidators.string.email).build();
368
-
369
- // Use presets for common patterns
370
- const counter = spPresets.counter({
371
- initial: 0,
372
- min: 0,
373
- max: 100,
374
- step: 1,
375
- withHistory: true,
376
- });
377
-
378
- const darkMode = spPresets.toggle({
379
- initial: false,
380
- persistent: true,
381
- storageKey: "theme-mode",
382
- });
383
- ```
384
-
385
- ### Schema Validation (Zod/Yup/Joi)
386
-
387
- Use any schema validation library with signals:
388
-
389
- ```typescript
390
- import { sp, spSchema, spSchemaValidator } from "ngx-signal-plus";
391
- import { z } from "zod";
392
-
393
- const userSchema = z.object({
394
- name: z.string().min(1),
395
- email: z.string().email(),
396
- age: z.number().min(18),
397
- });
398
-
399
- // Basic boolean validation with SignalBuilder
400
- const user = sp({ name: "", email: "", age: 0 }).validate(spSchema(userSchema)).build();
401
-
402
- // Advanced: Get detailed error messages
403
- const validator = spSchemaValidator(userSchema);
404
- const result = validator.validateWithErrors({ name: "", email: "invalid", age: 10 });
405
- // result: { valid: false, errors: ["name: String must contain at least 1 character(s)", "email: Invalid email", "age: Number must be greater than or equal to 18"] }
406
-
407
- // Use with SignalBuilder for boolean validation
408
- const validatedSignal = sp({ name: "", email: "", age: 0 }).validate(validator.validate).build();
409
- ```
410
-
411
- ### Middleware/Plugin System
412
-
413
- Intercept signal operations for logging, analytics, and error tracking:
414
-
415
- ```typescript
416
- import { spUseMiddleware, spLoggerMiddleware, spAnalyticsMiddleware } from "ngx-signal-plus";
417
-
418
- // Built-in logger middleware
419
- spUseMiddleware(spLoggerMiddleware("[DEBUG]"));
420
-
421
- // Custom analytics middleware
422
- spUseMiddleware(
423
- spAnalyticsMiddleware((event) => {
424
- analytics.track("signal_change", event);
425
- }),
426
- );
427
-
428
- // Custom middleware
429
- spUseMiddleware({
430
- name: "error-tracker",
431
- onSet: (ctx) => console.log(`${ctx.signalName}: ${ctx.oldValue} -> ${ctx.newValue}`),
432
- onError: (error) => Sentry.captureException(error),
433
- });
434
- ```
435
-
436
- ### State Management
437
-
438
- ```typescript
439
- import { spStorageManager, sp } from "ngx-signal-plus";
440
-
441
- // Storage management (saves to localStorage with namespace prefix)
442
- spStorageManager.save("app-settings", { theme: "dark", language: "en" });
443
- const settings = spStorageManager.load<{ theme: string; language: string }>("app-settings");
444
-
445
- // Remove when no longer needed
446
- spStorageManager.remove("app-settings");
447
-
448
- // History management through signals
449
- const counter = sp(0)
450
- .withHistory(10) // Keep last 10 values
451
- .build();
452
-
453
- counter.setValue(1);
454
- counter.setValue(2);
455
- counter.setValue(3);
456
-
457
- // Navigate history
458
- counter.undo(); // Back to 2
459
- counter.undo(); // Back to 1
460
- counter.redo(); // Forward to 2
461
-
462
- // Check history
463
- console.log(counter.history()); // Array of past values
464
- ```
465
-
466
- ### Cleanup and Memory Management
467
-
468
- **ngx-signal-plus** provides automatic and manual cleanup to prevent memory leaks:
469
-
470
- ```typescript
471
- import { sp } from "ngx-signal-plus";
472
-
473
- // Automatic cleanup when all subscribers unsubscribe
474
- const signal = sp(0).persist("counter").debounce(300).build();
475
- const unsubscribe = signal.subscribe((value) => console.log(value));
476
-
477
- // When you're done with the signal
478
- unsubscribe(); // Automatically cleans up when last subscriber unsubscribes
479
-
480
- // Manual cleanup with destroy()
481
- const signal2 = sp(0).persist("data").withHistory(10).build();
482
- signal2.setValue(42);
483
-
484
- // Explicitly destroy and clean up all resources
485
- signal2.destroy(); // Removes event listeners, clears timers, frees memory
486
- ```
487
-
488
- **What gets cleaned up:**
489
-
490
- - ✅ Storage event listeners (for `localStorage` synchronization)
491
- - ✅ Debounce/throttle timers
492
- - ✅ All subscribers
493
- - ✅ Pending operations
494
-
495
- **SSR-Safe:** All cleanup operations work safely in server-side rendering environments.
496
-
497
- ### Transactions and Batching
498
-
499
- Group multiple updates together with automatic rollback on errors:
500
-
501
- ```typescript
502
- import { spTransaction, spBatch } from "ngx-signal-plus";
503
-
504
- const balance = sp(100).build();
505
- const cart = sp<string[]>([]).build();
506
-
507
- // Transaction with automatic rollback
508
- try {
509
- spTransaction(() => {
510
- balance.setValue(balance.value() - 50);
511
- cart.update((items) => [...items, "premium-item"]);
512
-
513
- if (balance.value() < 0) {
514
- throw new Error("Insufficient funds");
515
- }
516
- // Success - changes are committed
517
- });
518
- } catch (error) {
519
- // Error - all changes automatically rolled back
520
- console.log(balance.value()); // 100 (original value)
521
- console.log(cart.value()); // [] (original value)
522
- }
523
-
524
- // Batch updates for performance (no rollback)
525
- spBatch(() => {
526
- signal1.setValue(1);
527
- signal2.setValue(2);
528
- signal3.setValue(3);
529
- // All changes applied together efficiently
530
- });
531
- ```
532
-
533
- ### Server-Side Rendering
534
-
535
- The library works seamlessly with Angular Universal:
536
-
537
- ```typescript
538
- // This code works in both SSR and browser
539
- const userPrefs = sp({ theme: "dark" }).persist("user-preferences").build();
540
-
541
- // In SSR: works in-memory, localStorage calls are safely skipped
542
- // In browser: full persistence with localStorage
543
- ```
544
-
545
- What happens during SSR:
69
+ ## Core APIs
546
70
 
547
- - Signals work normally with in-memory state
548
- - localStorage operations are safely skipped (no errors)
549
- - State automatically persists once the app runs in the browser
71
+ - Signal creation: `sp`, `spCounter`, `spToggle`, `spForm`, `spComputed`
72
+ - Signal enhancement: `enhance`
73
+ - Operators: `spMap`, `spFilter`, `spDebounceTime`, `spThrottleTime`, `spDelay`, `spDistinctUntilChanged`
74
+ - Forms and groups: `spForm`, `spFormGroup`
75
+ - Async helpers: `spAsync`, `spCollection`
76
+ - Reactive queries: `spQuery`, `spMutation`, `QueryClient`, `setGlobalQueryClient`
77
+ - Transactions: `spTransaction`, `spBatch`
78
+ - Schema validation: `spSchema`, `spSchemaValidator`
79
+ - Middleware: `spUseMiddleware`, `spRemoveMiddleware`, `spLoggerMiddleware`, `spAnalyticsMiddleware`
550
80
 
551
- ## Available Features
81
+ ## Comparisons
552
82
 
553
- | Category | Features |
554
- | --------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------- |
555
- | **Signal Creation** | `sp`, `spCounter`, `spToggle`, `spForm`, `spComputed` |
556
- | **Signal Enhancement** | `enhance`, validation, transformation, persistence, history |
557
- | **Signal Operators** | `spMap`, `spFilter`, `spDebounceTime`, `spThrottleTime`, `spDelay`, `spDistinctUntilChanged`, `spSkip`, `spTake`, `spMerge`, `spCombineLatest` |
558
- | **Form Groups** | `spFormGroup` - Group multiple controls with aggregated state, validation, and persistence |
559
- | **Async State Management** | `spAsync` - Manage asynchronous operations with loading, error, retry, and caching |
560
- | **Collection Management** | `spCollection` - Manage arrays of entities with ID-based CRUD, queries, transforms, and history |
561
- | **Transactions & Batching** | `spTransaction`, `spBatch`, `spIsTransactionActive`, `spIsInTransaction`, `spIsInBatch`, `spGetModifiedSignals` |
562
- | **Utilities** | `spValidators`, `spPresets`, `spSchema`, `spSchemaValidator` |
563
- | **Middleware/Plugins** | `spUseMiddleware`, `spRemoveMiddleware`, `spLoggerMiddleware`, `spAnalyticsMiddleware` |
564
- | **State Management** | `spHistoryManager`, `spStorageManager` |
565
- | **Components** | `spSignalPlusComponent`, `spSignalPlusService`, `spSignalBuilder` |
83
+ ### ngx-signal-plus vs Angular native signals
566
84
 
567
- ## Bundle Size Optimization
568
-
569
- The library is built with tree-shaking and optimization in mind. You only pay for what you use.
570
-
571
- ### Modern Package Exports
572
-
573
- The package provides **modular exports** for selective importing:
574
-
575
- ```typescript
576
- // Import only what you need - tree-shaking removes unused code
577
-
578
- // Core signals only (~3KB gzipped)
579
- import { sp, spCounter, spToggle } from "ngx-signal-plus/core";
580
-
581
- // Operators only (~2KB gzipped)
582
- import { spMap, spFilter, spDebounceTime } from "ngx-signal-plus/operators";
583
-
584
- // Utilities only (~2KB gzipped)
585
- import { enhance, spValidators, spPresets } from "ngx-signal-plus/utils";
586
-
587
- // State managers (~1KB gzipped)
588
- import { spHistoryManager, spStorageManager } from "ngx-signal-plus";
589
-
590
- // Everything (~8KB gzipped)
591
- import { sp, spMap, spFilter, enhance, spValidators } from "ngx-signal-plus";
592
- ```
85
+ - Angular provides core signal primitives (signal, computed, effect) and now also resource/httpResource for async patterns.
86
+ - ngx-signal-plus focuses on higher-level utilities on top of signals: validation, persistence, undo/redo, middleware, transactions, collections, and query-style helpers.
87
+ - Angular resource and httpResource are still marked experimental in Angular docs.
593
88
 
594
- ### Tree-Shaking Configuration
89
+ ### ngx-signal-plus vs NgRx Signals (@ngrx/signals)
595
90
 
596
- The package is optimized for tree-shaking:
91
+ - NgRx Signals is a full state-management approach centered on Signal Store architecture (store features, methods/hooks, and structured app state patterns).
92
+ - ngx-signal-plus is intentionally lighter: composable utilities that keep you close to native Angular signal usage without adopting a full store architecture.
93
+ - @ngrx/signals is actively maintained (current npm line is 20.x).
597
94
 
598
- - ✅ **`sideEffects: false`** in package.json - marks the library as side-effect free
599
- - ✅ **Modular exports** - separate entry points for each feature category
600
- - ✅ **ES2022 modules** - modern JavaScript with full tree-shaking support
601
- - ✅ **FESM bundles** - Flat ESM bundles for better optimization
602
- - ✅ **Individual entry points** for granular control:
603
- - `ngx-signal-plus/core` - Core signal creation
604
- - `ngx-signal-plus/operators` - Signal operators
605
- - `ngx-signal-plus/utils` - Utilities and validators
606
- - `ngx-signal-plus/models` - TypeScript types
95
+ ### ngx-signal-plus vs TanStack Query (Angular)
607
96
 
608
- ### Best Practices for Minimal Bundle
97
+ - TanStack Query is a dedicated server-state library (fetching, cache lifecycle, invalidation, retries, mutations).
98
+ - The Angular adapter package is @tanstack/angular-query-experimental, and TanStack currently labels it experimental.
99
+ - ngx-signal-plus includes query-style capabilities inside one package that also covers local signal utilities.
609
100
 
610
- **1. Import only what you need:**
611
-
612
- ```typescript
613
- // ✅ Good - imports only used features
614
- import { sp, spCounter } from "ngx-signal-plus";
615
-
616
- // ❌ Avoid - imports everything even if unused
617
- import * as SignalPlus from "ngx-signal-plus";
618
- ```
619
-
620
- **2. Use named imports:**
621
-
622
- ```typescript
623
- // ✅ Good - tree-shaking can remove unused exports
624
- import { sp, spMap } from "ngx-signal-plus";
625
-
626
- // ❌ Less optimal - may import more than needed
627
- import SignalPlus from "ngx-signal-plus";
628
- ```
629
-
630
- **3. Import from specific entry points:**
631
-
632
- ```typescript
633
- // ✅ Good - direct import from feature module
634
- import { spMap, spFilter } from "ngx-signal-plus/operators";
635
-
636
- // ✅ Also good - barrel export handles tree-shaking
637
- import { spMap, spFilter } from "ngx-signal-plus";
638
- ```
639
-
640
- ### Typical Bundle Sizes
641
-
642
- | Feature Set | Size (gzipped) | Savings vs Full |
643
- | --------------- | -------------- | --------------- |
644
- | Just `sp()` | ~1.5 KB | -87% |
645
- | Core signals | ~3 KB | -62% |
646
- | + Operators | ~5 KB | -38% |
647
- | + All utilities | ~8 KB | 0% |
648
-
649
- ### Performance Impact
650
-
651
- - **Tree-shaking enabled**: Webpack, Vite, Rollup automatically remove unused code
652
- - **No performance penalty**: Modern bundlers handle optimization automatically
653
- - **Zero runtime overhead**: Only loaded features are included
101
+ ### ngx-signal-plus vs Akita
654
102
 
103
+ - Akita is a store-centric architecture built around RxJS stores/queries.
104
+ - ngx-signal-plus is signal-first and utility-first, designed for composable local/global signal state without store boilerplate.
105
+ - Akita is no longer actively evolving like modern signal-first tools: the npm package is old (8.0.1, last published years ago), and the GitHub repository is archived.
655
106
  ## Documentation
656
107
 
657
- For detailed documentation including all features, API reference, and examples, see our [API Documentation](https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/docs/API.md).
658
-
659
- ## Contributing
660
-
661
- Please read our [Contributing Guide](https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/CONTRIBUTING.md).
662
-
663
- ## Support
664
-
665
- - [Documentation](https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/docs/API.md)
666
- - [Issue Tracker](https://github.com/milad-hub/ngx-signal-plus/issues)
108
+ - API documentation: https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/docs/API.md
109
+ - Contributing guide: https://github.com/milad-hub/ngx-signal-plus/blob/main/projects/signal-plus/CONTRIBUTING.md
110
+ - Issues: https://github.com/milad-hub/ngx-signal-plus/issues
667
111
 
668
112
  ## License
669
113