@myrmidon/paged-data-browsers 5.2.0 → 5.2.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,59 +1,149 @@
1
1
  # Paged Data Browsers
2
2
 
3
- - [Paged Data Browsers](#paged-data-browsers)
4
- - [Paged List](#paged-list)
5
- - [Paged Tree](#paged-tree)
6
- - [Nodes](#nodes)
7
- - [TreeNode](#treenode)
8
- - [PagedTreeNode](#pagedtreenode)
9
- - [Services](#services)
10
- - [PagedTreeStoreService](#pagedtreestoreservice)
11
- - [EditablePagedTreeStoreService](#editablepagedtreestoreservice)
12
- - [PagedTreeStore](#pagedtreestore)
13
- - [Node Component](#node-component)
14
- - [History](#history)
15
- - [5.2.0](#520)
16
- - [5.1.3](#513)
17
- - [5.1.2](#512)
18
- - [5.1.1](#511)
19
- - [5.1.0](#510)
20
- - [5.0.2](#502)
21
- - [5.0.1](#501)
22
- - [5.0.0](#500)
23
- - [4.0.2](#402)
24
- - [4.0.1](#401)
25
- - [4.0.0](#400)
26
- - [3.0.0](#300)
27
- - [2.0.5](#205)
28
- - [2.0.4](#204)
29
- - [2.0.3](#203)
30
- - [2.0.2](#202)
31
- - [2.0.1](#201)
32
- - [2.0.0](#200)
33
- - [1.1.0](#110)
34
-
35
- This library provides simple components to display filtered and paged data from some service.
36
-
37
- There are currently two components, one for displaying a flat list of data, and another to display hierarchically structured data, combining a tree view with paging.
38
-
39
- Also, there is a LRU cache service and a compact pager used in the paged tree component.
3
+ - 📦 `@myrmidon/paged-data-browsers`
4
+
5
+ This library provides components to display filtered and paged data from some service:
6
+
7
+ - a flat paged list of data.
8
+ - a paged tree, to display hierarchically structured data, combining a tree view with paging.
9
+ - a compact pager component for the tree.
10
+ - a LRU cache service.
11
+
12
+ ---
40
13
 
41
14
  ## Paged List
42
15
 
43
- Paged list support is provided by a single utility class, [PagedListStore](src/lib/services/paged-list.store.ts). This templated class provides logic for:
16
+ Paged list is provided by a single utility class, [PagedListStore](src/lib/services/paged-list.store.ts). This templated class provides:
44
17
 
45
18
  - a filter object of type `F`;
46
19
  - a list element object of type `E`.
47
20
 
48
- So you need:
21
+ You provide:
49
22
 
50
- - a filter class to represent your elements filter (for `F`).
23
+ - a filter class to represent the filter for list elements (for `F`).
51
24
  - an element item class (for `E`).
52
- - a service to fetch data, implementing interface [PagedListStoreService<F, E>](src/lib/services/paged-list.store.ts). In this shell, a [local service](../../../src/app/services/mock-paged-list-store.service.ts) provides mock data.
25
+ - a service to fetch data, implementing interface [PagedListStoreService<F, E>](src/lib/services/paged-list.store.ts).
26
+
27
+ >In this workspace demo app, a [local service](../../../src/app/services/mock-paged-list-store.service.ts) provides mock data.
28
+
29
+ ### Using Paged List
30
+
31
+ ▶️ (1) create a **filter dumb component** which just gets the filter and emits it when changes.
32
+
33
+ - [paged list filter code example](../../../src/app/components/paged-list-filter/paged-list-filter.component.ts)
34
+ - [paged list filter template example](../../../src/app/components/paged-list-filter/paged-list-filter.component.html)
35
+ - [paged list filter styles example](../../../src/app/components/paged-list-filter/paged-list-filter.component.css)
36
+
37
+ ▶️ (2) to persist list state, either **create a service wrapper** or an implementation of `PagedListStoreService<F,E>` for the store. This way the store will be used as a singleton with your data service.
38
+
39
+ - If you only need a service wrapper with no additional logic, just wrap it and expose the store as a property (see [paged list browser service example](src/app/services/paged-list-browser.service.ts)):
40
+
41
+ ```ts
42
+ /**
43
+ * Wrapper for a PagedListStore<F, E> instance.
44
+ * This singleton service ensures that the corresponding component
45
+ * preserves its state when navigating away and back.
46
+ */
47
+ @Injectable({
48
+ providedIn: 'root',
49
+ })
50
+ export class PagedListBrowserService {
51
+ public readonly store: PagedListStore<__F__Filter, __E__>;
52
+
53
+ constructor(service: __S__Service) {
54
+ this.store = new PagedListStore<__F__Filter, __E__>(service);
55
+ }
56
+ }
57
+ ```
58
+
59
+ - If instead you have additional data and logic in your component, make your service implement `PagedListStoreService<F,E>` and expose wrapped functionality from the store; in addition, it will also include other data (e.g. lookup data) and their services, to fully back the browser component UI.
60
+
61
+ If you implement this interface, you must provide a `loadPage` method which will be used by the list browser infrastructure to load the requested page; while other methods will just wrap essential functions of the store.
62
+
63
+ For instance, here is how you can implement `loadPage` and wrap store functionality in a `DocumentRepository implements PagedListStoreService<DocumentFilter, Document>` singleton service:
64
+
65
+ ```ts
66
+ // implement interface
67
+ private _store: PagedListStore<DocumentFilter, Document>;
68
+
69
+ constructor(private _docService: DocumentService) {
70
+ this._store = new PagedListStore<DocumentFilter, Document>(this);
71
+ this._loading$ = new BehaviorSubject<boolean>(false);
72
+ // ... etc.
73
+ this._store.reset();
74
+ }
75
+
76
+ public loadPage(
77
+ pageNumber: number,
78
+ pageSize: number,
79
+ filter: DocumentFilter
80
+ ): Observable<DataPage<Document>> {
81
+ this._loading$.next(true);
82
+ return this._docService.getDocuments(filter, pageNumber, pageSize).pipe(
83
+ tap({
84
+ next: () => this._loading$.next(false),
85
+ error: () => this._loading$.next(false),
86
+ })
87
+ );
88
+ }
89
+
90
+ // wrap store functions
91
+ public async reset(): Promise<void> {
92
+ this._loading$.next(true);
93
+ try {
94
+ await this._store.reset();
95
+ } catch (error) {
96
+ throw error;
97
+ } finally {
98
+ this._loading$.next(false);
99
+ }
100
+ }
101
+
102
+ public async setFilter(filter: DocumentFilter): Promise<void> {
103
+ this._loading$.next(true);
104
+ try {
105
+ await this._store.setFilter(filter);
106
+ } catch (error) {
107
+ throw error;
108
+ } finally {
109
+ this._loading$.next(false);
110
+ }
111
+ }
112
+
113
+ public getFilter(): DocumentFilter {
114
+ return this._store.getFilter();
115
+ }
116
+
117
+ public async setPage(pageNumber: number, pageSize: number): Promise<void> {
118
+ this._loading$.next(true);
119
+ try {
120
+ await this._store.setPage(pageNumber, pageSize);
121
+ } catch (error) {
122
+ throw error;
123
+ } finally {
124
+ this._loading$.next(false);
125
+ }
126
+ }
127
+ ```
128
+
129
+ ▶️ (3) **create your browser component** which gets the service at (2) injected, and exposes from its wrapped store the filter and page observables, handling filter and page changes:
130
+
131
+ - [paged list browser code example](src/app/components/paged-list-browser/paged-list-browser.component.ts)
132
+ - [paged list browser template example](src/app/components/paged-list-browser/paged-list-browser.component.html)
133
+ - [paged list browser styles example](src/app/components/paged-list-browser/paged-list-browser.component.css)
134
+
135
+ ---
53
136
 
54
137
  ## Paged Tree
55
138
 
56
- The paged tree is a tree where each node can page and filter its direct children.
139
+ A paged tree is a tree with flat nodes (of type `PagedTreeNode<F>`), where nodes are dynamically loaded and paged. The tree has two **filters** (equal to or derived from `TreeNodeFilter`):
140
+
141
+ - a _global_ filter, applied to all the pages it handles;
142
+ - _per-node_ filters, which apply to the node's children to determine their page.
143
+
144
+ When expanding a node, a page of children nodes is fetched from a configured service. Each parent node maintains paging information about the current page of its children, orchestrating its filter and page number.
145
+
146
+ ![overview](./img/paged-tree.png)
57
147
 
58
148
  ### Nodes
59
149
 
@@ -71,27 +161,19 @@ A paged tree is based on **nodes** of type `TreeNode` or its derived types. This
71
161
 
72
162
  #### PagedTreeNode
73
163
 
74
- A **paged tree node** type (`PagedTreeNode<F>`) derives from this basic `TreeNode`, adding two essential components for filtering and paging its children:
75
-
76
- - a _filter_ for its children. Node filters derive from `TreeNodeFilter`, which just refers to `TreeNode`'s properties: so, the base filter just has a tag and a parent ID. Typically you derive your own filter from this type.
77
-
78
- >⚠️ It is very important to take into account the parent ID property of the base filter when implementing the paging service.
164
+ A **paged tree node** (`PagedTreeNode<F>` where `F` is the type of the nodes filter) derives from `TreeNode` adding:
79
165
 
80
- - _paging information_ for its children. This is of type `PagingInfo` which has page number, page items count, and total items count.
81
-
82
- The base type for a paged tree node is thus `PagedTreeNode<F>`, where `F` is the type of the filter used for filtering nodes; this adds properties:
83
-
84
- - `paging`: paging information. This is required.
166
+ - `paging`: paging information for children nodes (required). This is of type `PagingInfo` which has page number, page items count, and total items count.
85
167
  - `expanded`: for expanded/collapsed state. Initially this is `undefined`.
86
- - `filter`: filter object. If not set, there will be no filtering and thus all the children will be included.
168
+ - `filter`: filter for children nodes. If not set, all children are included.
169
+
170
+ >Node filters derive from `TreeNodeFilter`, which refers to `TreeNode`'s properties: so, the base filter has only a tag and a parent ID. Typically you derive your own filter from this type. In your filter logic it is very important to take into account the parent ID property of the base filter!
87
171
 
88
172
  ### Services
89
173
 
90
174
  #### PagedTreeStoreService
91
175
 
92
- Typically, data for the tree is dynamically loaded. It can also be loaded all at once; in both cases, data will always be provided by a service implementing the same interface. This is the paged tree store's service, which fetches data from your data source on behalf of the paged tree store.
93
-
94
- So, your data must be provided by this service which implements interface `PagedTreeStoreService<F>`, where `F` is the tree node filter type. The interface requires you to implement a single function, `getNodes`, which returns a specific page of nodes, applying the specified filter:
176
+ Data for the tree is dynamically loaded or loaded all at once by a service implementing `PagedTreeStoreService<F>`, where `F` is the tree node filter type. This interface requires to implement `getNodes` to fetch nodes with filtering and paging:
95
177
 
96
178
  ```ts
97
179
  getNodes(
@@ -102,7 +184,14 @@ getNodes(
102
184
  ): Observable<DataPage<TreeNode>>;
103
185
  ```
104
186
 
105
- >This service deals with `TreeNode`'s (or their derivations), returning a specific page of them. It is the service which is directly in contact with your data source, and its only purpose is to return a page of nodes. It is the store which extends these nodes with paging and filtering data using the `PagedTreeNode` type. Note that if you want a single root mock node, your implementation of this service must provide it (with Y=0).
187
+ >This service deals with `TreeNode`'s (or their derivations), returning a specific page of them. It is the service which is directly in contact with your data source, and its only purpose is to return a page of nodes. The store extends these nodes with paging and filtering using `PagedTreeNode`. If you want a single root mock node, your implementation of this service must provide it (with Y=0).
188
+
189
+ The `getNodes` method can also get a mock-root parameter referring to two data models:
190
+
191
+ - when you have a data model with a single root node for the whole tree, this parameter is `false`, because the root node comes from your data. It will have Y=1 and X=1, and contain all the tree nodes as its descendants.
192
+ - when instead your data lacks a single root node, and at its root level has many root nodes, this parameter can be either `true` or `false`. When `false`, multiple root level nodes are allowed but they are all loaded at once. So this is useful only when your root nodes number is limited. When instead the parameter is `true`, it is up to your service implementation to provide a mock root node for the multiple root nodes, which become its children. This mock root will have Y=0.
193
+
194
+ >As a sample, compare the [mock service](./src/app/services/paged-tree-browser.service.ts) used in this shell. Its `getData` method gets this parameter and when true it creates a root node with Y=0, X=1, and id=1; in this case, the root nodes are made children of it. Otherwise, it just goes with multiple root nodes. In the example shell, you are allowed to toggle this parameter to see these two models in action. In a real-world application instead you will usually stick to a single model.
106
195
 
107
196
  #### EditablePagedTreeStoreService
108
197
 
@@ -134,16 +223,14 @@ A base implementation `EditablePagedTreeStoreServiceBase<F>` is provided that ha
134
223
  - `fetchNodes()`: Get nodes from your actual data source
135
224
  - `persistChanges()`: Save changes to your data source
136
225
 
137
- This design ensures full backward compatibility - existing readonly implementations continue to work unchanged.
138
-
139
226
  #### PagedTreeStore
140
227
 
141
- The tree logic is implemented by the `PagedTreeStore<E,F>`, where:
228
+ The tree logic is implemented by the top-level class `PagedTreeStore<E,F>`, where:
142
229
 
143
230
  - `E` is the element (node) type (a `PagedTreeNode<F>` or any derived type);
144
231
  - `F` is the filter type (a `TreeNodeFilter` or any derived type).
145
232
 
146
- The store is used to load and manage a flat list of nodes. Every tree node in the list is extended with page number, page count and total items, plus expansion-related metadata. Users can expand and collapse nodes, browse through pages of children, and filter them.
233
+ The store is used to load and manage a flat list of nodes. Every tree node in the list is extended with page number, page count and total items, plus expansion-state metadata. Users can expand and collapse nodes, browse through pages of children, and filter them.
147
234
 
148
235
  When using an `EditablePagedTreeStoreService`, the store also supports editing operations:
149
236
 
@@ -165,7 +252,6 @@ All editing operations automatically handle:
165
252
  The essential store **data** are:
166
253
 
167
254
  - the flat _list of paged nodes_ (exposed in `nodes$`). This flat list is populated by using an instance of the paged tree store's service (`PagedTreeStoreService<F>`), injected in the store constructor together with its options (of type `PagedTreeStoreOptions`). Among other things, the options specify whether there must be a single mock root node, and the default page size. Once retrieved, pages can be fetched from an internal LRU cache (which is cleared when calling `reset`).
168
- - a list of tree _tags_ (exposed in `tags$`).
169
255
  - a _global filter_ (exposed in `filter$`). This gets combined with (overridden by) node-specific filters, when specified. You can set it with `setFilter`. To set the filter for the children of a specific node use `setNodeFilter`.
170
256
  - the _page size_ (`pageSize`), a get/set property, initially set by the options injected in the store constructor. Setting this property resets the store (like calling `reset`).
171
257
 
@@ -185,11 +271,15 @@ The main **methods** are:
185
271
  - `expand(id)`: expand the specified node (if it has children and is collapsed).
186
272
  - `expandAll(id)`: expand all the descendants of the specified node.
187
273
  - `collapse(id)`: collapse the specified node if expanded.
188
- - `collapseAll()`: collpase all the descendants of the specified node.
274
+ - `collapseAll(id?)`: collapse all the descendants of the specified node. When called without an argument, all expanded root-level nodes are collapsed.
189
275
 
190
276
  - `changePage(parentId, pageNumber)`: change the page of children nodes of the specified parent node.
191
277
  - `setFilter(filter)`: sets the global filter, resetting the store.
192
278
  - `setNodeFilter(id, filter)`: sets the node-filter for the specified node, resetting its children page number to 1.
279
+ - `findLabels(searchText)`: search all nodes (loading lazily as needed) for labels containing the search text, highlight matched nodes, and expand their ancestors so they become visible.
280
+ - `removeHilites()`: remove all search highlights from every node.
281
+ - `ensureNodeVisible(id, expanded?, refresh?)`: find a node by ID, expand all its ancestors, navigate to the page containing it, optionally set its expanded state, and highlight it. Useful for programmatic navigation to a specific node.
282
+ - `getAnchorForDeletedNode(id)`: returns the ID of the node that should be selected after deleting the given node (next sibling → previous sibling → parent → null). Useful for keeping focus meaningful after a deletion.
193
283
 
194
284
  The store is a plain TypeScript class. If you want to **persist** it in the app, you can wrap it in a service and inject the service into your component. If you don't need this, just implement your data service to provide nodes via `getNodes`. Otherwise, you can wrap the store like e.g. here using a singleton for a single page of nodes in the demo app:
195
285
 
@@ -259,113 +349,160 @@ You should provide your components for:
259
349
  - a filters component for global filters. This dummy component gets a `filter$` and emits `filterChange`.
260
350
  - a tree view.
261
351
 
262
- ## History
263
-
264
- ### 5.2.0
265
-
266
- - 2026-03-02: ⚠️ migrated to `OnPush`.
352
+ ### Using Tree
267
353
 
268
- ### 5.1.3
354
+ ▶️ (1) to define your node, **create a node class** extending `PagedTreeNode<F>`. This parent (derived from `TreeNode`) already has properties for IDs, label, filtering and paging. In your derived class you should add data linked to the node. For example, here data is just the `count` property:
269
355
 
270
- - 2026-01-13: adding ensure node visible functionality to the paged tree store so that we can preserve focus on node when updating it in backend data.
271
- - 2026-01-12:
272
- - updated Angular and packages.
273
- - refactored thesauri components moving them from demo app to a new `@myrmidon/cadmus-thesaurus-store` library. Once the components here reach production stage, the library will import `ThesaurusEntry` from the Cadmus core package and be packaged and exported to become part of the Cadmus system.
274
- - fixes to editable thesaurus component.
275
- - 2026-01-07: updated Angular and packages.
276
-
277
- ### 5.1.2
278
-
279
- - 2025-11-22:
280
- - ⚠️ upgraded to Angular 21.
281
- - ⚠️ migrated to `pnpm`.
282
-
283
- ### 5.1.1
284
-
285
- - 2025-10-05:
286
- - fixes in editable tree store.
287
- - minor improvements in paged tree store.
288
- - better documentation.
289
-
290
- ### 5.1.0
291
-
292
- - 2025-09-23:
293
- - added thesaurus tree demo page in app.
294
- - added `findLabels` and `removeHilites` to tree store.
295
- - updated package and tsconfig for library (version 5.0.3).
296
-
297
- ### 5.0.2
298
-
299
- - 2025-06-15:
300
- - fix to tree expand all.
301
- - updated Angular and packages.
302
-
303
- ### 5.0.1
304
-
305
- - 2025-06-10: removed dirty check in reset. When reset is requested, just do it.
306
-
307
- ### 5.0.0
308
-
309
- - 2025-05-29: ⚠️ upgraded to Angular 20.
310
- - 2025-05-25: updated Angular and packages.
311
-
312
- ### 4.0.2
313
-
314
- - 2025-01-01: updated packages.
315
-
316
- ### 4.0.1
317
-
318
- - 2024-12-04: added CSS variables to browser tree node.
319
-
320
- ### 4.0.0
356
+ ```ts
357
+ export interface MockTreeNode extends PagedTreeNode<TreeNodeFilter> {
358
+ count: number;
359
+ }
360
+ ```
321
361
 
322
- - 2024-12-02: ⚠️ refactored code:
323
- - replaced dependencies with new standalone `ngx-mat-...` libraries.
324
- - upgraded to standalone.
325
- - upgraded to functional injection.
326
- - upgraded to use signal-based properties and events.
327
- - fixes to tree store for collapse.
362
+ ▶️ (2) to define your filter, **create a filter class** for this node, extending `TreeNodeFilter` (this parent just provides `tags` and `parentId`). For example:
328
363
 
329
- ### 3.0.0
364
+ ```ts
365
+ export interface MockTreeFilter extends TreeNodeFilter {
366
+ label?: string;
367
+ minCount?: number;
368
+ maxCount?: number;
369
+ }
370
+ ```
330
371
 
331
- - 2024-11-22: ⚠️ upgraded to Angular 19.
372
+ ▶️ (3) **create a service** implementing `PagedTreeStoreService<F>` to fetch a page of nodes from some data source (using filter of type `F`). For example:
332
373
 
333
- ### 2.0.5
374
+ ```ts
375
+ @Injectable({
376
+ providedIn: 'root',
377
+ })
378
+ export class MockPagedTreeStoreService
379
+ implements PagedTreeStoreService<MockTreeFilter>
380
+ {
381
+ /**
382
+ * Get the specified page of nodes.
383
+ * @param filter The filter.
384
+ * @param pageNumber The page number.
385
+ * @param pageSize The page size.
386
+ * @param hasMockRoot Whether the root node is a mock node.
387
+ */
388
+ public getNodes(
389
+ filter: MockTreeFilter,
390
+ pageNumber: number,
391
+ pageSize: number,
392
+ hasMockRoot?: boolean
393
+ ): Observable<DataPage<MockTreeNode>> {
394
+ // TODO fetch nodes using filter and pageNumber, pageSize
395
+ // and return the requested page of nodes with items,
396
+ // pageNumber, pageSize, pageCount, total.
397
+ }
398
+ }
399
+ ```
334
400
 
335
- - 2024-10-24: added `hideLoc` to tree node component.
401
+ >⚠️ Always remember to **filter by parent ID** in your implementation of `getNodes`! For instance, here is a filter by label, which first filters by parent ID:
336
402
 
337
- ### 2.0.4
403
+ ```ts
404
+ /**
405
+ * Get the specified page of nodes.
406
+ * @param filter The filter.
407
+ * @param pageNumber The page number.
408
+ * @param pageSize The page size.
409
+ * @param hasMockRoot Whether the root node is a mock node. Not used here.
410
+ */
411
+ public getNodes(
412
+ filter: ThesEntryNodeFilter,
413
+ pageNumber: number,
414
+ pageSize: number,
415
+ hasMockRoot?: boolean
416
+ ): Observable<DataPage<ThesEntryPagedTreeNode>> {
417
+ this.ensureNodes();
418
+
419
+ // apply filtering
420
+ let nodes = this._nodes.filter((n) => {
421
+ if (filter.parentId !== undefined && filter.parentId !== null) {
422
+ if (n.parentId !== filter.parentId) {
423
+ return false;
424
+ }
425
+ } else {
426
+ if (n.parentId) {
427
+ return false;
428
+ }
429
+ }
430
+
431
+ if (filter.label) {
432
+ const filterValue = filter.label.toLowerCase();
433
+ if (!n.label.toLowerCase().includes(filterValue)) {
434
+ return false;
435
+ }
436
+ }
437
+ return true;
438
+ });
439
+
440
+ // apply paging
441
+ const startIndex = (pageNumber - 1) * pageSize;
442
+ const endIndex = startIndex + pageSize;
443
+ const pagedNodes = nodes.slice(startIndex, endIndex);
444
+
445
+ // page and return
446
+ const paged = nodes.slice(
447
+ (pageNumber - 1) * pageSize,
448
+ pageNumber * pageSize
449
+ );
450
+ return of({
451
+ items: paged,
452
+ pageNumber: pageNumber,
453
+ pageSize: pageSize,
454
+ pageCount: Math.ceil(nodes.length / pageSize),
455
+ total: nodes.length,
456
+ });
457
+ }
458
+ ```
338
459
 
339
- - 2024-10-23:
340
- - updated Angular.
341
- - updated package peer dependencies.
342
- - added i18n.
343
- - added `hideFilter` to paged tree browser.
460
+ ▶️ (4) filter UI: create a **paged tree filter dumb component** which just gets the filter and emits it when changes. This component will be used both as the global filter editor and as the per-node filters editor. In the latter case, it will appear as a popup. So you need to inject a `MatDialogRef` and its data in the constructor, and call dialog ref's `close` function when changing the filter. Also, note that in the HTML template we add margin when the filter component is wrapped into a dialog.
344
461
 
345
- ### 2.0.3
462
+ - [paged tree filter example code](src/app/components/paged-tree-filter/paged-tree-filter.component.ts)
463
+ - [paged tree filter example template](src/app/components/paged-tree-filter/paged-tree-filter.component.html)
464
+ - [paged tree filter example styles](src/app/components/paged-tree-filter/paged-tree-filter.component.css)
346
465
 
347
- - 2024-10-16: added `clear` method to stores.
466
+ ▶️ (5) (optional) singleton service wrapper: if you want to persist the tree state, **create a service wrapper** for the store which gets instantiated with your data fetch service (implementing interface `PagedTreeStoreService<F>`), like in this example. This wrapper will also provide app-specific options for configuring the store, including the `hasMockRoot` parameter, should it be required.
348
467
 
349
- ### 2.0.2
468
+ ```ts
469
+ // paged-tree-browser.service.ts
470
+ import { Injectable } from '@angular/core';
350
471
 
351
- - 2024-10-02: fix to paged tree store page change when any nodes in old page are expanded.
472
+ @Injectable({
473
+ providedIn: 'root',
474
+ })
475
+ export class PagedTreeBrowserService {
476
+ public readonly store: PagedTreeStore<__N__Node, __F__Filter>;
352
477
 
353
- ### 2.0.1
478
+ constructor(service: __S__Service) {
479
+ this.store = new PagedTreeStore<__N__Node, __F__Filter>(service);
480
+ }
481
+ }
482
+ ```
354
483
 
355
- - 2024-10-02:
356
- - updated Angular and packages.
357
- - added get root node method to tree store.
358
- - more comments.
484
+ If instead you are going to directly use the service, inject the service in a new store, in your browser component (see nr.6) e.g.:
359
485
 
360
- ### 2.0.0
486
+ ```ts
487
+ /**
488
+ * The store instance, built from the service.
489
+ */
490
+ public readonly store = computed(() => {
491
+ const service = this.service();
492
+ const store = new PagedTreeStore<
493
+ ThesEntryPagedTreeNode,
494
+ ThesEntryNodeFilter
495
+ >(service);
496
+ this.nodes$ = store.nodes$;
497
+ this.filter$ = store.filter$;
498
+ return store;
499
+ });
500
+ ```
361
501
 
362
- - 2024-08-04: ⚠️ breaking changes to tree browser: refactored to remove tag-based multiple trees.
363
- - 2024-08-02:
364
- - upgraded Angular and packages.
365
- - replaced `color` with `class`.
366
- - refactored styles for new Material.
502
+ ▶️ (6) tree-view browser: **create a tree browser component** which gets the wrapper service at (5) injected, and exposes from its wrapped store the filter and page observables, handling filter and page changes:
367
503
 
368
- ### 1.1.0
504
+ - [paged tree browser code example](src/app/components/paged-tree-browser/paged-tree-browser.component.ts)
505
+ - [paged tree browser template example](src/app/components/paged-tree-browser/paged-tree-browser.component.html)
506
+ - [paged tree browser styles example](src/app/components/paged-tree-browser/paged-tree-browser.component.css)
369
507
 
370
- - 2024-05-23: upgraded to Angular 18.
371
- - 2023-11-09: upgraded to Angular 17.
508
+ 💡 If you want to add search by label with multiple matches highlight, add a form to your component and call the corresponding methods of the store service, like in the [thesaurus paged browser tree component sample](./src/app/components/thesaurus-paged-tree-browser/thesaurus-paged-tree-browser.component.ts) (`findLabels`, `removeHilites`).