1
1
import { NgTemplateOutlet } from '@angular/common' ;
2
2
import {
3
3
ChangeDetectionStrategy ,
4
- ChangeDetectorRef ,
5
4
Component ,
6
5
ElementRef ,
7
- EventEmitter ,
8
- HostBinding ,
9
- HostListener ,
10
6
inject ,
11
- Input ,
12
7
OnDestroy ,
13
8
OnInit ,
14
- Output ,
15
- TemplateRef
9
+ TemplateRef ,
10
+ input ,
11
+ output ,
12
+ numberAttribute ,
13
+ computed ,
14
+ booleanAttribute
16
15
} from '@angular/core' ;
17
16
import { fromEvent , Subscription , takeUntil } from 'rxjs' ;
18
17
19
- import { InnerSortEvent , TableColumnInternal } from '../../types/internal.types' ;
18
+ import {
19
+ InnerSortEvent ,
20
+ SortableTableColumnInternal ,
21
+ TableColumnInternal
22
+ } from '../../types/internal.types' ;
20
23
import {
21
24
HeaderCellContext ,
22
25
SelectionType ,
@@ -32,37 +35,38 @@ import { nextSortDir } from '../../utils/sort';
32
35
imports : [ NgTemplateOutlet ] ,
33
36
template : `
34
37
<div class="datatable-header-cell-template-wrap">
35
- @if (isTarget) {
38
+ @if (isTarget() ) {
36
39
<ng-template
37
- [ngTemplateOutlet]="targetMarkerTemplate!"
38
- [ngTemplateOutletContext]="targetMarkerContext"
40
+ [ngTemplateOutlet]="targetMarkerTemplate() !"
41
+ [ngTemplateOutletContext]="targetMarkerContext() "
39
42
/>
40
43
}
41
- @if (isCheckboxable) {
44
+ @if (isCheckboxable() ) {
42
45
<label class="datatable-checkbox">
43
46
<input
44
47
type="checkbox"
45
- [attr.aria-label]="ariaHeaderCheckboxMessage"
46
- [checked]="allRowsSelected"
48
+ [attr.aria-label]="ariaHeaderCheckboxMessage() "
49
+ [checked]="allRowsSelected() "
47
50
(change)="select.emit()"
48
51
/>
49
52
</label>
50
53
}
54
+ @let column = this.column();
51
55
@if (column.headerTemplate) {
52
56
<ng-template
53
57
[ngTemplateOutlet]="column.headerTemplate"
54
- [ngTemplateOutletContext]="cellContext"
58
+ [ngTemplateOutletContext]="cellContext() "
55
59
/>
56
60
} @else {
57
61
<span class="datatable-header-cell-wrapper">
58
62
<span class="datatable-header-cell-label draggable" (click)="onSort()">
59
- {{ name }}
63
+ {{ name() }}
60
64
</span>
61
65
</span>
62
66
}
63
- <span [class]="sortClass" (click)="onSort()"> </span>
67
+ <span [class]="sortClass() " (click)="onSort()"> </span>
64
68
</div>
65
- @if (showResizeHandle) {
69
+ @if (showResizeHandle() ) {
66
70
<span
67
71
class="resize-handle"
68
72
(mousedown)="onMousedown($event)"
@@ -73,88 +77,62 @@ import { nextSortDir } from '../../utils/sort';
73
77
styleUrl : './header-cell.component.scss' ,
74
78
changeDetection : ChangeDetectionStrategy . OnPush ,
75
79
host : {
76
- 'class' : 'datatable-header-cell' ,
77
- '[attr.resizeable]' : 'showResizeHandle'
80
+ '[attr.resizeable]' : 'showResizeHandle()' ,
81
+ '[attr.title]' : 'name()' ,
82
+ '[attr.tabindex]' : 'column().sortable ? 0 : -1' ,
83
+ '[class]' : 'columnCssClasses()' ,
84
+ '[style.height.px]' : 'headerHeight()' ,
85
+ '[style.minWidth.px]' : 'column().minWidth' ,
86
+ '[style.maxWidth.px]' : 'column().maxWidth' ,
87
+ '[style.width.px]' : 'column().width' ,
88
+ '(contextmenu)' : 'onContextmenu($event)' ,
89
+ '(keydown.enter)' : 'enter()'
78
90
}
79
91
} )
80
92
export class DataTableHeaderCellComponent implements OnInit , OnDestroy {
81
- private cd = inject ( ChangeDetectorRef ) ;
82
-
83
- @Input ( ) sortType ! : SortType ;
84
- @Input ( ) sortAscendingIcon ?: string ;
85
- @Input ( ) sortDescendingIcon ?: string ;
86
- @Input ( ) sortUnsetIcon ?: string ;
87
-
88
- @Input ( ) isTarget ?: boolean ;
89
- @Input ( ) showResizeHandle ?: boolean = true ;
90
- @Input ( ) targetMarkerTemplate ?: TemplateRef < any > ;
91
- @Input ( ) targetMarkerContext : any ;
92
- @Input ( ) enableClearingSortState = false ;
93
- @Input ( ) ariaHeaderCheckboxMessage ! : string ;
94
-
95
- private _allRowsSelected ?: boolean ;
96
-
97
- @Input ( ) set allRowsSelected ( value ) {
98
- this . _allRowsSelected = value ;
99
- this . cellContext . allRowsSelected = value ;
100
- }
101
- get allRowsSelected ( ) {
102
- return this . _allRowsSelected ;
103
- }
104
-
105
- @Input ( ) selectionType ?: SelectionType ;
106
-
107
- @Input ( ) set column ( column : TableColumnInternal ) {
108
- this . _column = column ;
109
- this . cellContext . column = column ;
110
- this . cd . markForCheck ( ) ;
111
- }
112
-
113
- get column ( ) : TableColumnInternal {
114
- return this . _column ;
115
- }
116
-
117
- @HostBinding ( 'style.height.px' )
118
- @Input ( )
119
- headerHeight ! : number ;
120
-
121
- @Input ( ) set sorts ( val : SortPropDir [ ] ) {
122
- this . _sorts = val ;
123
- this . sortDir = this . calcSortDir ( val ) ;
124
- this . cellContext . sortDir = this . sortDir ;
125
- this . sortClass = this . calcSortClass ( this . sortDir ) ;
126
- this . cd . markForCheck ( ) ;
127
- }
128
-
129
- get sorts ( ) : SortPropDir [ ] {
130
- return this . _sorts ;
131
- }
132
-
133
- @Output ( ) readonly sort = new EventEmitter < InnerSortEvent > ( ) ;
134
- @Output ( ) readonly select = new EventEmitter < void > ( ) ;
135
- @Output ( ) readonly columnContextmenu = new EventEmitter < {
93
+ readonly sortType = input . required < SortType > ( ) ;
94
+ readonly sortAscendingIcon = input < string > ( ) ;
95
+ readonly sortDescendingIcon = input < string > ( ) ;
96
+ readonly sortUnsetIcon = input < string > ( ) ;
97
+
98
+ readonly isTarget = input < boolean > ( ) ;
99
+ readonly showResizeHandle = input < boolean | undefined > ( true ) ;
100
+ readonly targetMarkerTemplate = input < TemplateRef < any > > ( ) ;
101
+ readonly targetMarkerContext = input < any > ( ) ;
102
+ readonly enableClearingSortState = input ( false ) ;
103
+ readonly ariaHeaderCheckboxMessage = input . required < string > ( ) ;
104
+ readonly allRowsSelected = input ( false , { transform : booleanAttribute } ) ;
105
+ readonly selectionType = input < SelectionType > ( ) ;
106
+ readonly column = input . required < TableColumnInternal > ( ) ;
107
+ readonly headerHeight = input . required < number , number > ( {
108
+ transform : numberAttribute
109
+ } ) ;
110
+ readonly sorts = input < SortPropDir [ ] > ( [ ] ) ;
111
+
112
+ readonly sort = output < InnerSortEvent > ( ) ;
113
+ readonly select = output < void > ( ) ;
114
+ readonly columnContextmenu = output < {
136
115
event : MouseEvent ;
137
116
column : TableColumnInternal ;
138
- } > ( false ) ;
139
- @ Output ( ) readonly resize = new EventEmitter < { width : number ; column : TableColumnInternal } > ( ) ;
140
- @ Output ( ) readonly resizing = new EventEmitter < { width : number ; column : TableColumnInternal } > ( ) ;
117
+ } > ( ) ;
118
+ readonly resize = output < { width : number ; column : TableColumnInternal } > ( ) ;
119
+ readonly resizing = output < { width : number ; column : TableColumnInternal } > ( ) ;
141
120
142
- @HostBinding ( 'class' )
143
- protected get columnCssClasses ( ) : string {
121
+ protected readonly columnCssClasses = computed ( ( ) => {
144
122
let cls = 'datatable-header-cell' ;
145
-
146
- if ( this . column . sortable ) {
123
+ const column = this . column ( ) ;
124
+ if ( column . sortable ) {
147
125
cls += ' sortable' ;
148
126
}
149
- if ( this . showResizeHandle ) {
127
+ if ( this . showResizeHandle ( ) ) {
150
128
cls += ' resizeable' ;
151
129
}
152
- if ( this . column . headerClass ) {
153
- if ( typeof this . column . headerClass === 'string' ) {
154
- cls += ' ' + this . column . headerClass ;
155
- } else if ( typeof this . column . headerClass === 'function' ) {
156
- const res = this . column . headerClass ( {
157
- column : this . column
130
+ if ( column . headerClass ) {
131
+ if ( typeof column . headerClass === 'string' ) {
132
+ cls += ' ' + column . headerClass ;
133
+ } else if ( typeof column . headerClass === 'function' ) {
134
+ const res = column . headerClass ( {
135
+ column
158
136
} ) ;
159
137
160
138
if ( typeof res === 'string' ) {
@@ -170,80 +148,55 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
170
148
}
171
149
}
172
150
173
- const sortDir = this . sortDir ;
151
+ const sortDir = this . sortDir ( ) ;
174
152
if ( sortDir ) {
175
153
cls += ` sort-active sort-${ sortDir } ` ;
176
154
}
177
155
178
156
return cls ;
179
- }
157
+ } ) ;
180
158
181
- @HostBinding ( 'attr.title' )
182
- protected get name ( ) : string | undefined {
159
+ protected readonly name = computed ( ( ) => {
183
160
// guaranteed to have a value by setColumnDefaults() in column-helper.ts
184
- return this . column . headerTemplate === undefined ? this . column . name : undefined ;
185
- }
186
-
187
- @HostBinding ( 'style.minWidth.px' )
188
- protected get minWidth ( ) : number | undefined {
189
- return this . column . minWidth ;
190
- }
191
-
192
- @HostBinding ( 'style.maxWidth.px' )
193
- protected get maxWidth ( ) : number | undefined {
194
- return this . column . maxWidth ;
195
- }
196
-
197
- @HostBinding ( 'style.width.px' )
198
- protected get width ( ) : number {
199
- return this . column . width ;
200
- }
201
-
202
- @HostBinding ( 'tabindex' ) protected get tabindex ( ) : number {
203
- return this . column . sortable ? 0 : - 1 ;
204
- }
205
-
206
- protected get isCheckboxable ( ) : boolean | undefined {
207
- return this . column . headerCheckboxable ;
208
- }
209
-
210
- protected sortClass ?: string ;
211
- private sortDir ?: SortDirection ;
212
-
213
- protected cellContext : HeaderCellContext ;
214
-
215
- private _column ! : TableColumnInternal ;
216
- private _sorts ! : SortPropDir [ ] ;
217
- private element = inject ( ElementRef ) . nativeElement ;
218
- private subscription ?: Subscription ;
219
-
220
- constructor ( ) {
221
- this . cellContext = {
222
- column : this . column ,
223
- sortDir : this . sortDir ,
161
+ return this . column ( ) . headerTemplate === undefined ? this . column ( ) . name : undefined ;
162
+ } ) ;
163
+
164
+ protected readonly isCheckboxable = computed ( ( ) => this . column ( ) . headerCheckboxable ) ;
165
+
166
+ protected readonly sortClass = computed < string | undefined > ( ( ) => {
167
+ return this . calcSortClass ( this . sortDir ( ) ) ;
168
+ } ) ;
169
+ private readonly sortDir = computed < SortDirection | undefined > ( ( ) => {
170
+ return this . calcSortDir ( this . sorts ( ) ) ;
171
+ } ) ;
172
+
173
+ protected readonly cellContext = computed < HeaderCellContext > ( ( ) => {
174
+ return {
175
+ column : this . column ( ) ,
176
+ sortDir : this . sortDir ( ) ,
224
177
sortFn : ( ) => this . onSort ( ) ,
225
- allRowsSelected : this . allRowsSelected ,
178
+ allRowsSelected : this . allRowsSelected ( ) ,
226
179
selectFn : ( ) => this . select . emit ( )
227
180
} ;
228
- }
181
+ } ) ;
182
+
183
+ private element = inject ( ElementRef ) . nativeElement ;
184
+ private subscription ?: Subscription ;
229
185
230
- @HostListener ( 'contextmenu' , [ '$event' ] )
231
186
protected onContextmenu ( $event : MouseEvent ) : void {
232
- this . columnContextmenu . emit ( { event : $event , column : this . column } ) ;
233
- if ( this . column . draggable ) {
187
+ this . columnContextmenu . emit ( { event : $event , column : this . column ( ) } ) ;
188
+ if ( this . column ( ) . draggable ) {
234
189
$event . preventDefault ( ) ;
235
190
}
236
191
}
237
192
238
- @HostListener ( 'keydown.enter' )
239
193
protected enter ( ) : void {
240
194
this . onSort ( ) ;
241
195
}
242
196
243
197
ngOnInit ( ) {
244
- this . sortClass = this . calcSortClass ( this . sortDir ) ;
245
198
// If there is already a default sort then start the counter with 1.
246
- if ( this . sortDir ) {
199
+ if ( this . sortDir ( ) ) {
247
200
this . totalSortStatesApplied = 1 ;
248
201
}
249
202
}
@@ -253,8 +206,8 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
253
206
}
254
207
255
208
private calcSortDir ( sorts : SortPropDir [ ] ) : any {
256
- if ( sorts && this . column ) {
257
- const sort = sorts . find ( ( s : any ) => s . prop === this . column . prop ) ;
209
+ if ( sorts && this . column ( ) ) {
210
+ const sort = sorts . find ( ( s : any ) => s . prop === this . column ( ) . prop ) ;
258
211
259
212
if ( sort ) {
260
213
return sort . dir ;
@@ -264,34 +217,34 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
264
217
// Counter to reset sort once user sort asc and desc.
265
218
private totalSortStatesApplied = 0 ;
266
219
protected onSort ( ) : void {
267
- if ( ! this . column . sortable ) {
220
+ if ( ! this . column ( ) . sortable ) {
268
221
return ;
269
222
}
270
223
271
224
this . totalSortStatesApplied ++ ;
272
- let newValue = nextSortDir ( this . sortType , this . sortDir ) ;
225
+ let newValue = nextSortDir ( this . sortType ( ) , this . sortDir ( ) ) ;
273
226
// User has done both direction sort so we reset the next sort.
274
- if ( this . enableClearingSortState && this . totalSortStatesApplied === 3 ) {
227
+ if ( this . enableClearingSortState ( ) && this . totalSortStatesApplied === 3 ) {
275
228
newValue = undefined ;
276
229
this . totalSortStatesApplied = 0 ;
277
230
}
278
231
this . sort . emit ( {
279
- column : this . column ,
280
- prevValue : this . sortDir ,
232
+ column : this . column ( ) as SortableTableColumnInternal < any > ,
233
+ prevValue : this . sortDir ( ) ,
281
234
newValue
282
235
} ) ;
283
236
}
284
237
285
238
private calcSortClass ( sortDir : SortDirection | undefined ) : string | undefined {
286
- if ( ! this . cellContext . column . sortable ) {
239
+ if ( ! this . cellContext ( ) . column . sortable ) {
287
240
return undefined ;
288
241
}
289
242
if ( sortDir === SortDirection . asc ) {
290
- return `sort-btn sort-asc ${ this . sortAscendingIcon ?? 'datatable-icon-up' } ` ;
243
+ return `sort-btn sort-asc ${ this . sortAscendingIcon ( ) ?? 'datatable-icon-up' } ` ;
291
244
} else if ( sortDir === SortDirection . desc ) {
292
- return `sort-btn sort-desc ${ this . sortDescendingIcon ?? 'datatable-icon-down' } ` ;
245
+ return `sort-btn sort-desc ${ this . sortDescendingIcon ( ) ?? 'datatable-icon-down' } ` ;
293
246
} else {
294
- return `sort-btn ${ this . sortUnsetIcon ?? 'datatable-icon-sort-unset' } ` ;
247
+ return `sort-btn ${ this . sortUnsetIcon ( ) ?? 'datatable-icon-sort-unset' } ` ;
295
248
}
296
249
}
297
250
@@ -317,7 +270,7 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
317
270
private onMouseup ( ) : void {
318
271
if ( this . subscription && ! this . subscription . closed ) {
319
272
this . destroySubscription ( ) ;
320
- this . resize . emit ( { width : this . element . clientWidth , column : this . column } ) ;
273
+ this . resize . emit ( { width : this . element . clientWidth , column : this . column ( ) } ) ;
321
274
}
322
275
}
323
276
@@ -328,7 +281,7 @@ export class DataTableHeaderCellComponent implements OnInit, OnDestroy {
328
281
) : void {
329
282
const movementX = getPositionFromEvent ( event ) . screenX - mouseDownScreenX ;
330
283
const newWidth = initialWidth + movementX ;
331
- this . resizing . emit ( { width : newWidth , column : this . column } ) ;
284
+ this . resizing . emit ( { width : newWidth , column : this . column ( ) } ) ;
332
285
}
333
286
334
287
private destroySubscription ( ) : void {
0 commit comments