@libs-ui/pipes-check-selected-by-key 0.2.356-9 → 0.2.357-0

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
@@ -2,32 +2,24 @@
2
2
 
3
3
  > Pipe kiểm tra xem một giá trị có tồn tại trong mảng các key đã chọn hay không.
4
4
 
5
- **Version:** 0.2.355-14
6
-
7
5
  ## Giới thiệu
8
6
 
9
- `LibsUiPipesCheckSelectedByKeyPipe` là một Angular pipe đơn giản nhưng hữu ích để kiểm tra xem một giá trị có tồn tại trong mảng các key đã được chọn hay không. Pipe này thường được sử dụng trong các trường hợp multi-select, checkbox lists, hoặc bất kỳ tình huống nào cần kiểm tra trạng thái "đã chọn" của một item.
7
+ `LibsUiPipesCheckSelectedByKeyPipe` là Angular pure pipe dùng để kiểm tra xem một giá trị có thuộc danh sách các key đã được chọn hay không. Pipe hỗ trợ mọi kiểu dữ liệu (string, number, object reference) và xử lý an toàn các trường hợp null/undefined. Đặc biệt, pipe sử dụng tham số `length` như một trigger để ép pure pipe chạy lại khi nội dung mảng bị mutate mà không thay đổi reference.
10
8
 
11
- ### Tính năng
9
+ ## Tính năng
12
10
 
13
- - ✅ Kiểm tra nhanh sự tồn tại của giá trị trong mảng
14
- - ✅ Hỗ trợ mọi kiểu dữ liệu (string, number, object)
15
- - ✅ Validation an toàn với null/undefined
16
- - ✅ Pure pipe - hiệu suất cao
17
- - ✅ Standalone - dễ dàng import
11
+ - ✅ Kiểm tra nhanh sự tồn tại của giá trị trong mảng bằng strict equality (`===`)
12
+ - ✅ Hỗ trợ mọi kiểu dữ liệu: `string`, `number`, object reference
13
+ - ✅ Xử an toàn `null` / `undefined` — luôn trả về `false` thay vì throw lỗi
14
+ - ✅ Tham số `length` là trigger giúp pure pipe re-run khi mảng bị mutate
15
+ - ✅ Standalone import trực tiếp vào component không cần NgModule
18
16
 
19
17
  ## Khi nào sử dụng
20
18
 
21
19
  - Kiểm tra xem một item có được chọn trong danh sách multi-select hay không
22
- - Hiển thị trạng thái selected/unselected trong UI (checkbox, highlight)
23
- - Filter hoặc validate dữ liệu dựa trên danh sách keys đã chọn
24
- - Xác định xem một key trong collection hay không
25
-
26
- ## ⚠️ Important Notes
27
-
28
- - **🔄 Tham số length - Pure Pipe Trigger**: Đây là pattern quan trọng để đảm bảo pipe chạy lại khi mảng thay đổi. Vì đây là pure pipe, nó chỉ chạy lại khi reference thay đổi. Nếu bạn `push`/`pop` phần tử vào mảng (mutate) mà giữ nguyên reference, pipe sẽ không tự chạy lại. Tham số `length` buộc pipe phải re-run khi số lượng phần tử thay đổi.
29
- - **Kiểm tra null/undefined**: Pipe trả về `false` nếu `multiKeys` là `null`, `undefined`, không phải array, hoặc array rỗng.
30
- - **So sánh strict**: Sử dụng `===` để so sánh, không có type coercion.
20
+ - Hiển thị trạng thái selected/unselected trong UI (checkbox, highlight row, active state)
21
+ - Xác định class CSS động dựa trên danh sách key đã chọn
22
+ - Filter hoặc validate dữ liệu dựa trên tập hợp keys đã chọn
31
23
 
32
24
  ## Cài đặt
33
25
 
@@ -48,146 +40,164 @@ import { LibsUiPipesCheckSelectedByKeyPipe } from '@libs-ui/pipes-check-selected
48
40
  export class YourComponent {}
49
41
  ```
50
42
 
51
- ## Ví dụ
43
+ ## Ví dụ sử dụng
52
44
 
53
- ### Cách sử dụng bản
54
-
55
- ```html
56
- <!-- Kiểm tra xem 'apple' có trong selectedFruits không -->
57
- <div [class.selected]="'apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : selectedFruits.length">Apple {{ ('apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : selectedFruits.length) ? '✓' : '' }}</div>
58
- ```
45
+ ### dụ 1 Multi-select highlight item
59
46
 
60
47
  ```typescript
61
- selectedFruits = ['apple', 'banana'];
62
- // Kết quả: true (vì 'apple' có trong mảng)
63
- ```
48
+ // component.ts
49
+ import { Component, signal } from '@angular/core';
50
+ import { LibsUiPipesCheckSelectedByKeyPipe } from '@libs-ui/pipes-check-selected-by-key';
64
51
 
65
- ### Sử dụng với \*ngFor
52
+ @Component({
53
+ standalone: true,
54
+ imports: [LibsUiPipesCheckSelectedByKeyPipe],
55
+ templateUrl: './fruit-list.component.html',
56
+ })
57
+ export class FruitListComponent {
58
+ protected selectedFruits = signal<string[]>(['apple', 'banana']);
59
+ protected allFruits = ['apple', 'banana', 'orange', 'grape'];
60
+
61
+ protected handlerToggleFruit(event: MouseEvent, fruit: string): void {
62
+ event.stopPropagation();
63
+ const current = [...this.selectedFruits()];
64
+ const index = current.indexOf(fruit);
65
+ if (index >= 0) {
66
+ current.splice(index, 1);
67
+ } else {
68
+ current.push(fruit);
69
+ }
70
+ this.selectedFruits.set(current);
71
+ }
72
+ }
73
+ ```
66
74
 
67
75
  ```html
68
- <div *ngFor="let fruit of allFruits">
69
- <div [class.active]="fruit.id | LibsUiPipesCheckSelectedByKeyPipe : selectedIds : selectedIds.length">{{ fruit.name }}</div>
70
- </div>
76
+ <!-- fruit-list.component.html -->
77
+ @for (fruit of allFruits; track fruit) {
78
+ <div
79
+ [class.bg-blue-100]="fruit | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits() : selectedFruits().length"
80
+ [class.border-blue-500]="fruit | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits() : selectedFruits().length"
81
+ class="p-3 border rounded cursor-pointer"
82
+ (click)="handlerToggleFruit($event, fruit)">
83
+ {{ fruit }}
84
+ @if (fruit | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits() : selectedFruits().length) {
85
+ <span>✓</span>
86
+ }
87
+ </div>
88
+ }
71
89
  ```
72
90
 
73
- ### Sử dụng với số
91
+ ### dụ 2 — Kiểm tra số trong danh sách ID đã chọn
74
92
 
75
- ```html
76
- <div [class.active]="42 | LibsUiPipesCheckSelectedByKeyPipe : selectedIds : selectedIds.length">Item 42</div>
93
+ ```typescript
94
+ // order-list.component.ts
95
+ import { Component } from '@angular/core';
96
+ import { LibsUiPipesCheckSelectedByKeyPipe } from '@libs-ui/pipes-check-selected-by-key';
97
+
98
+ @Component({
99
+ standalone: true,
100
+ imports: [LibsUiPipesCheckSelectedByKeyPipe],
101
+ templateUrl: './order-list.component.html',
102
+ })
103
+ export class OrderListComponent {
104
+ protected selectedIds = [1, 42, 100];
105
+ protected orders = [
106
+ { id: 1, name: 'Order #1' },
107
+ { id: 42, name: 'Order #42' },
108
+ { id: 99, name: 'Order #99' },
109
+ ];
110
+ }
77
111
  ```
78
112
 
79
- ```typescript
80
- selectedIds = [1, 42, 100];
81
- // Kết quả: true
113
+ ```html
114
+ <!-- order-list.component.html -->
115
+ @for (order of orders; track order.id) {
116
+ <div [class.active]="order.id | LibsUiPipesCheckSelectedByKeyPipe : selectedIds : selectedIds.length">
117
+ {{ order.name }}
118
+ </div>
119
+ }
120
+ <!-- Order #1 → active (true) -->
121
+ <!-- Order #42 → active (true) -->
122
+ <!-- Order #99 → không active (false) -->
82
123
  ```
83
124
 
84
- ### Edge Cases
125
+ ### dụ 3 — Kiểm tra với giá trị undefined / mảng rỗng (edge cases)
85
126
 
86
127
  ```html
87
- <!-- Mảng rỗng -->
128
+ <!-- Mảng rỗng → luôn false -->
88
129
  {{ 'apple' | LibsUiPipesCheckSelectedByKeyPipe : [] : 0 }}
89
130
  <!-- Kết quả: false -->
90
131
 
91
- <!-- multiKeys undefined -->
132
+ <!-- multiKeys undefined → luôn false -->
92
133
  {{ 'apple' | LibsUiPipesCheckSelectedByKeyPipe : undefined : undefined }}
93
134
  <!-- Kết quả: false -->
94
- ```
95
-
96
- ## API
97
135
 
98
- ### LibsUiPipesCheckSelectedByKeyPipe
99
-
100
- ```typescript
101
- value | LibsUiPipesCheckSelectedByKeyPipe : multiKeys : length
136
+ <!-- length = 0 dù mảng có phần tử → false (không dùng cách này) -->
137
+ {{ 'apple' | LibsUiPipesCheckSelectedByKeyPipe : ['apple'] : 0 }}
138
+ <!-- Kết quả: false — BUG nếu bạn không truyền đúng length -->
102
139
  ```
103
140
 
104
- #### Parameters
105
-
106
- | Property | Type | Default | Description |
107
- | ----------- | ------------------------- | ------- | ------------------------------------------------------------------ |
108
- | `value` | `any` | - | Giá trị cần kiểm tra |
109
- | `multiKeys` | `Array<any> \| undefined` | - | Mảng các key đã được chọn |
110
- | `length` | `number \| undefined` | - | **Pure Pipe Trigger** - Truyền `multiKeys.length` để đảm bảo pipe chạy lại khi mảng thay đổi số lượng phần tử |
111
-
112
- #### Returns
113
-
114
- `boolean` - `true` nếu `value` tồn tại trong `multiKeys`, ngược lại `false`
141
+ ### Ví dụ 4 — Sử dụng standalone (pipe.transform())
115
142
 
116
- ## Hidden Logic
143
+ ```typescript
144
+ import { LibsUiPipesCheckSelectedByKeyPipe } from '@libs-ui/pipes-check-selected-by-key';
117
145
 
118
- ### 1. 🔄 Pure Pipe Trigger Pattern (Tham số `length`)
146
+ const pipe = new LibsUiPipesCheckSelectedByKeyPipe();
119
147
 
120
- Tham số `length` một **trigger parameter** quan trọng để giải quyết vấn đề của pure pipe trong Angular:
148
+ pipe.transform('apple', ['apple', 'banana'], 2); // true
149
+ pipe.transform('orange', ['apple', 'banana'], 2); // false
150
+ pipe.transform('apple', [], 0); // false
151
+ pipe.transform('apple', undefined, undefined); // false
152
+ pipe.transform(42, [1, 42, 100], 3); // true
153
+ pipe.transform('42', [42], 1); // false (strict equality)
154
+ ```
121
155
 
122
- **Vấn đề**: Angular pure pipe chỉ chạy lại khi reference của input thay đổi. Nếu bạn mutate mảng (dùng `push`, `pop`, `splice`) mà vẫn giữ nguyên reference, pipe sẽ không tự động chạy lại.
156
+ ## Transform
123
157
 
124
- **Giải pháp**: Truyền `length` như một tham số riêng. Khi số lượng phần tử thay đổi, Angular detect change và buộc pipe phải chạy lại.
158
+ | Tham số | Type | Bắt buộc | tả | dụ |
159
+ |---|---|---|---|---|
160
+ | `value` | `any` | Có | Giá trị cần kiểm tra sự tồn tại trong mảng | `'apple'`, `42`, `myObj` |
161
+ | `multiKeys` | `Array<any> \| undefined` | Có | Mảng các key đã được chọn | `['apple', 'banana']`, `[1, 42]` |
162
+ | `length` | `number \| undefined` | Có | **Pure Pipe Trigger** — BẮT BUỘC truyền `multiKeys.length` để pipe re-run khi mảng bị mutate | `selectedFruits.length` |
125
163
 
126
- **🔴 QUAN TRỌNG - `length` PHẢI được trích xuất chính xác từ `multiKeys.length`:**
164
+ **Trả về:** `boolean` `true` nếu `value` tồn tại trong `multiKeys`, ngược lại `false`.
127
165
 
128
- ```typescript
129
- // ✅ Đúng: Truyền multiKeys.length
130
- 'apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : selectedFruits.length
166
+ **Cú pháp template:**
131
167
 
132
- // ❌ Sai: Truyền sai length
133
- 'apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : 0
134
- // => false (mặc 'apple' có trong mảng!)
168
+ ```html
169
+ {{ value | LibsUiPipesCheckSelectedByKeyPipe : multiKeys : length }}
170
+ [class.selected]="value | LibsUiPipesCheckSelectedByKeyPipe : multiKeys : length"
135
171
  ```
136
172
 
137
- **Cách hoạt động:**
173
+ ## Lưu ý quan trọng
138
174
 
139
- ```typescript
140
- // 1. Khi mutate array (push/pop), reference không đổi
141
- this.selectedKeys.push('new-key'); // Cùng reference
175
+ ⚠️ **Tham số `length` là Pure Pipe Trigger — BẮT BUỘC truyền đúng**: Vì đây là pure pipe, Angular chỉ chạy lại khi input thay đổi reference. Khi bạn mutate mảng bằng `push()` / `pop()` / `splice()` mà giữ nguyên reference, pipe sẽ không tự chạy lại. Tham số `length` được truyền vào để ép Angular detect change và buộc pipe re-run khi số lượng phần tử thay đổi. Luôn truyền `myArray.length` — không được hardcode số cố định như `: 0` hay `: 1`.
142
176
 
143
- // 2. Nhưng nhờ truyền selectedKeys.length, Angular detect change
144
- // Template: value | pipe : selectedKeys : selectedKeys.length
145
- // => Pipe chạy lại length thay đổi
177
+ ```html
178
+ <!-- ĐÚNG pipe sẽ re-run khi selectedFruits thay đổi -->
179
+ {{ 'apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : selectedFruits.length }}
146
180
 
147
- // 3. Validation trong source code
148
- if (!multiKeys || !(multiKeys instanceof Array) || !multiKeys.length || !length) {
149
- return false; // ❌ Return false nếu length không khớp
150
- }
181
+ <!-- SAI pipe KHÔNG re-run vì length cố định -->
182
+ {{ 'apple' | LibsUiPipesCheckSelectedByKeyPipe : selectedFruits : 0 }}
151
183
  ```
152
184
 
153
- ### 2. Validation đầu vào
185
+ ⚠️ **So sánh strict equality (`===`)**: Pipe dùng `===` để so sánh, không có type coercion. `'42'` `42` là hai giá trị khác nhau. Object được so sánh theo reference, không theo value.
154
186
 
155
- Pipe có logic validation phức tạp để đảm bảo an toàn khi xử lý các giá trị null/undefined:
187
+ ```html
188
+ <!-- String '42' ≠ Number 42 → false -->
189
+ {{ '42' | LibsUiPipesCheckSelectedByKeyPipe : [42] : 1 }}
156
190
 
157
- ```typescript
158
- // Các trường hợp trả về false:
159
- // 1. multiKeys là null hoặc undefined
160
- // 2. multiKeys không phải là Array
161
- // 3. multiKeys.length === 0
162
- // 4. length parameter là falsy (0, null, undefined)
163
-
164
- // Edge case:
165
- // Nếu length = 0 nhưng multiKeys có phần tử
166
- // Pipe vẫn trả về false (do check !length)
167
- const result = 'apple' | pipe : ['apple'] : 0;
168
- // => false (mặc dù 'apple' có trong array)
191
+ <!-- Cùng reference object → true -->
192
+ <!-- Khác reference cùng nội dung → false -->
169
193
  ```
170
194
 
171
- ### 3. So sánh Strict Equality
172
-
173
- Pipe sử dụng strict equality (`===`) để so sánh, không có type coercion:
174
-
175
- ```typescript
176
- // String vs Number
177
- '42' | pipe : [42] : 1 // => false
178
-
179
- // Object reference
180
- const obj = { id: 1 };
181
- obj | pipe : [obj] : 1 // => true
182
- obj | pipe : [{ id: 1 }] : 1 // => false (khác reference)
183
- ```
195
+ ⚠️ **Pipe trả về `false` cho mọi input không hợp lệ**: `multiKeys` là `null`, `undefined`, không phải array, hoặc array rỗng đều trả về `false`. Tham số `length` là falsy (0, null, undefined) cũng trả về `false` — kể cả khi `multiKeys` có phần tử.
184
196
 
185
197
  ## Demo
186
198
 
187
- **Local:** http://localhost:4500/pipes/check-selected-by-key
188
-
189
- **Production:** https://your-domain.com/pipes/check-selected-by-key
190
-
191
- ## License
199
+ ```bash
200
+ npx nx serve core-ui
201
+ ```
192
202
 
193
- MIT
203
+ Truy cập: http://localhost:4500/pipes/check-selected-by-key
@@ -1 +1 @@
1
- {"version":3,"file":"libs-ui-pipes-check-selected-by-key.mjs","sources":["../../../../../libs-ui/pipes/check-selected-by-key/src/check-selected-by-key.pipe.ts","../../../../../libs-ui/pipes/check-selected-by-key/src/libs-ui-pipes-check-selected-by-key.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Pipe, PipeTransform } from '@angular/core';\n@Pipe({\n name: 'LibsUiPipesCheckSelectedByKeyPipe',\n standalone: true,\n})\nexport class LibsUiPipesCheckSelectedByKeyPipe implements PipeTransform {\n transform(value: any, multiKeys: Array<any> | undefined, length: number | undefined): boolean {\n if (!multiKeys || !(multiKeys instanceof Array) || !multiKeys.length || !length) {\n return false;\n }\n\n return multiKeys.some((key) => key === value);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAAA;MAMa,iCAAiC,CAAA;AAC5C,IAAA,SAAS,CAAC,KAAU,EAAE,SAAiC,EAAE,MAA0B,EAAA;AACjF,QAAA,IAAI,CAAC,SAAS,IAAI,EAAE,SAAS,YAAY,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE;AAC/E,YAAA,OAAO,KAAK;QACd;AAEA,QAAA,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,KAAK,CAAC;IAC/C;wGAPW,iCAAiC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,IAAA,EAAA,CAAA;sGAAjC,iCAAiC,EAAA,YAAA,EAAA,IAAA,EAAA,IAAA,EAAA,mCAAA,EAAA,CAAA;;4FAAjC,iCAAiC,EAAA,UAAA,EAAA,CAAA;kBAJ7C,IAAI;AAAC,YAAA,IAAA,EAAA,CAAA;AACJ,oBAAA,IAAI,EAAE,mCAAmC;AACzC,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA;;;ACLD;;AAEG;;;;"}
1
+ {"version":3,"file":"libs-ui-pipes-check-selected-by-key.mjs","sources":["../../../../../libs-ui/pipes/check-selected-by-key/src/check-selected-by-key.pipe.ts","../../../../../libs-ui/pipes/check-selected-by-key/src/libs-ui-pipes-check-selected-by-key.ts"],"sourcesContent":["/* eslint-disable @typescript-eslint/no-explicit-any */\nimport { Pipe, PipeTransform } from '@angular/core';\n@Pipe({\n name: 'LibsUiPipesCheckSelectedByKeyPipe',\n standalone: true,\n})\nexport class LibsUiPipesCheckSelectedByKeyPipe implements PipeTransform {\n transform(value: any, multiKeys: Array<any> | undefined, length: number | undefined): boolean {\n if (!multiKeys || !(multiKeys instanceof Array) || !multiKeys.length || !length) {\n return false;\n }\n\n return multiKeys.some((key) => key === value);\n }\n}\n","/**\n * Generated bundle index. Do not edit.\n */\n\nexport * from './index';\n"],"names":[],"mappings":";;;AAAA;MAMa,iCAAiC,CAAA;AAC5C,IAAA,SAAS,CAAC,KAAU,EAAE,SAAiC,EAAE,MAA0B,EAAA;AACjF,QAAA,IAAI,CAAC,SAAS,IAAI,EAAE,SAAS,YAAY,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,MAAM,EAAE;AAC/E,YAAA,OAAO,KAAK,CAAC;SACd;AAED,QAAA,OAAO,SAAS,CAAC,IAAI,CAAC,CAAC,GAAG,KAAK,GAAG,KAAK,KAAK,CAAC,CAAC;KAC/C;wGAPU,iCAAiC,EAAA,IAAA,EAAA,EAAA,EAAA,MAAA,EAAA,EAAA,CAAA,eAAA,CAAA,IAAA,EAAA,CAAA,CAAA;sGAAjC,iCAAiC,EAAA,YAAA,EAAA,IAAA,EAAA,IAAA,EAAA,mCAAA,EAAA,CAAA,CAAA;;4FAAjC,iCAAiC,EAAA,UAAA,EAAA,CAAA;kBAJ7C,IAAI;AAAC,YAAA,IAAA,EAAA,CAAA;AACJ,oBAAA,IAAI,EAAE,mCAAmC;AACzC,oBAAA,UAAU,EAAE,IAAI;AACjB,iBAAA,CAAA;;;ACLD;;AAEG;;;;"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@libs-ui/pipes-check-selected-by-key",
3
- "version": "0.2.356-9",
3
+ "version": "0.2.357-0",
4
4
  "peerDependencies": {
5
5
  "@angular/core": ">=18.0.0"
6
6
  },