@ -5,21 +5,18 @@ import {
Component ,
Component ,
computed ,
computed ,
ContentChild ,
ContentChild ,
EventEmitter ,
inject ,
inject ,
Injector ,
Injector ,
Input ,
LOCALE_ID ,
LOCALE_ID ,
OnChanges ,
OnDestroy ,
OnDestroy ,
Output ,
PLATFORM_ID ,
PLATFORM_ID ,
signal ,
signal ,
SimpleChanges ,
TemplateRef ,
TemplateRef ,
TrackByFunction ,
TrackByFunction ,
ViewChild ,
ViewChild ,
input
input ,
effect ,
output
} from '@angular/core' ;
} from '@angular/core' ;
import { AsyncPipe , isPlatformBrowser , NgComponentOutlet , NgTemplateOutlet } from '@angular/common' ;
import { AsyncPipe , isPlatformBrowser , NgComponentOutlet , NgTemplateOutlet } from '@angular/common' ;
@ -47,7 +44,7 @@ import {
import { ePropType } from '../../enums/props.enum' ;
import { ePropType } from '../../enums/props.enum' ;
import { EntityActionList } from '../../models/entity-actions' ;
import { EntityActionList } from '../../models/entity-actions' ;
import { EntityProp , EntityPropList } from '../../models/entity-props' ;
import { EntityProp , EntityPropList } from '../../models/entity-props' ;
import { PropData } from '../../models/props' ;
import { Readonly PropData } from '../../models/props' ;
import { ExtensionsService } from '../../services/extensions.service' ;
import { ExtensionsService } from '../../services/extensions.service' ;
import {
import {
ENTITY_PROP_TYPE_CLASSES ,
ENTITY_PROP_TYPE_CLASSES ,
@ -87,7 +84,7 @@ const DEFAULT_ACTIONS_COLUMN_WIDTH = 150;
}
}
` ],
` ],
} )
} )
export class ExtensibleTableComponent < R = any > implements OnChanges , AfterViewInit , OnDestroy {
export class ExtensibleTableComponent < R = any > implements AfterViewInit , OnDestroy {
readonly # injector = inject ( Injector ) ;
readonly # injector = inject ( Injector ) ;
readonly getInjected = this . # injector . get . bind ( this . # injector ) ;
readonly getInjected = this . # injector . get . bind ( this . # injector ) ;
protected readonly cdr = inject ( ChangeDetectorRef ) ;
protected readonly cdr = inject ( ChangeDetectorRef ) ;
@ -99,54 +96,56 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
private platformId = inject ( PLATFORM_ID ) ;
private platformId = inject ( PLATFORM_ID ) ;
protected isBrowser = isPlatformBrowser ( this . platformId ) ;
protected isBrowser = isPlatformBrowser ( this . platformId ) ;
protected _actionsText ! : string ;
// Input signals
@Input ( )
readonly actionsTextInput = input < string | undefined > ( undefined , { alias : 'actionsText' } ) ;
set actionsText ( value : string ) {
readonly dataInput = input < R [ ] > ( [ ] , { alias : 'data' } ) ;
this . _actionsText = value ;
}
get actionsText ( ) : string {
return this . _actionsText ? ? ( this . actionList . length >= 1 ? 'AbpUi::Actions' : '' ) ;
}
@Input ( ) data ! : R [ ] ;
readonly list = input . required < ListService > ( ) ;
readonly list = input . required < ListService > ( ) ;
readonly recordsTotal = input . required < number > ( ) ;
readonly recordsTotal = input . required < number > ( ) ;
readonly actionsColumnWidthInput = input < number | undefined > ( undefined , { alias : 'actionsColumnWidth' } ) ;
@Input ( ) set actionsColumnWidth ( width : number ) {
readonly actionsTemplate = input < TemplateRef < any > | undefined > ( undefined ) ;
this . _actionsColumnWidth . set ( width ? Number ( width ) : undefined ) ;
}
@Input ( ) actionsTemplate? : TemplateRef < any > ;
@Output ( ) tableActivate = new EventEmitter ( ) ;
readonly selectable = input ( false ) ;
readonly selectable = input ( false ) ;
readonly selectionTypeInput = input < SelectionType | string > ( SelectionType . multiClick , { alias : 'selectionType' } ) ;
@Input ( ) set selectionType ( value : SelectionType | string ) {
this . _selectionType = typeof value === 'string' ? SelectionType [ value ] : value ;
}
_selectionType : SelectionType = SelectionType . multiClick ;
readonly selected = input < any [ ] > ( [ ] ) ;
readonly selected = input < any [ ] > ( [ ] ) ;
@Output ( ) selectionChange = new EventEmitter < any [ ] > ( ) ;
// Infinite scroll configuration
readonly infiniteScroll = input ( false ) ;
readonly infiniteScroll = input ( false ) ;
readonly isLoading = input ( false ) ;
readonly isLoading = input ( false ) ;
readonly scrollThreshold = input ( 10 ) ;
readonly scrollThreshold = input ( 10 ) ;
@Output ( ) loadMore = new EventEmitter < void > ( ) ;
readonly tableHeight = input < number | undefined > ( undefined ) ;
readonly tableHeight = input < number > ( undefined ) ;
readonly rowDetailTemplate = input < TemplateRef < RowDetailContext < R > > | undefined > ( undefined ) ;
readonly rowDetailTemplate = input < TemplateRef < RowDetailContext < R > >> ( undefined ) ;
readonly rowDetailHeight = input < string | number > ( '100%' ) ;
readonly rowDetailHeight = input < string | number > ( '100%' ) ;
@Output ( ) rowDetailToggle = new EventEmitter < R > ( ) ;
// Output signals
readonly tableActivate = output < any > ( ) ;
readonly selectionChange = output < any [ ] > ( ) ;
readonly loadMore = output < void > ( ) ;
readonly rowDetailToggle = output < R > ( ) ;
// Internal signals
protected readonly _data = signal < R [ ] > ( [ ] ) ;
private readonly _actionsColumnWidth = signal < number | undefined > ( DEFAULT_ACTIONS_COLUMN_WIDTH ) ;
@ContentChild ( ExtensibleTableRowDetailComponent )
@ContentChild ( ExtensibleTableRowDetailComponent )
rowDetailComponent? : ExtensibleTableRowDetailComponent < R > ;
rowDetailComponent? : ExtensibleTableRowDetailComponent < R > ;
@ViewChild ( 'table' , { static : false } ) table ! : DatatableComponent ;
@ViewChild ( 'table' , { static : false } ) table ! : DatatableComponent ;
// Computed values
protected readonly actionsText = computed ( ( ) = > {
return this . actionsTextInput ( ) ? ? ( this . actionList . length >= 1 ? 'AbpUi::Actions' : '' ) ;
} ) ;
protected readonly selectionType = computed ( ( ) = > {
const value = this . selectionTypeInput ( ) ;
return typeof value === 'string' ? SelectionType [ value as keyof typeof SelectionType ] : value ;
} ) ;
protected get data ( ) : R [ ] {
return this . _data ( ) ;
}
protected set data ( value : R [ ] ) {
this . _data . set ( value ) ;
}
protected get effectiveRowDetailTemplate ( ) : TemplateRef < RowDetailContext < R > > | undefined {
protected get effectiveRowDetailTemplate ( ) : TemplateRef < RowDetailContext < R > > | undefined {
return this . rowDetailComponent ? . template ( ) ? ? this . rowDetailTemplate ( ) ;
return this . rowDetailComponent ? . template ( ) ? ? this . rowDetailTemplate ( ) ;
}
}
@ -163,9 +162,6 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
readonly trackByFn : TrackByFunction < EntityProp < R > > = ( _ , item ) = > item . name ;
readonly trackByFn : TrackByFunction < EntityProp < R > > = ( _ , item ) = > item . name ;
// Signal for actions column width
private readonly _actionsColumnWidth = signal < number | undefined > ( DEFAULT_ACTIONS_COLUMN_WIDTH ) ;
// Infinite scroll: debounced load more subject
// Infinite scroll: debounced load more subject
private readonly loadMoreSubject = new Subject < void > ( ) ;
private readonly loadMoreSubject = new Subject < void > ( ) ;
private readonly loadMoreSubscription = this . loadMoreSubject
private readonly loadMoreSubscription = this . loadMoreSubject
@ -187,6 +183,53 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
this . permissionService . filterItemsByPolicy (
this . permissionService . filterItemsByPolicy (
this . actionList . toArray ( ) . map ( action = > ( { requiredPolicy : action.permission } ) ) ,
this . actionList . toArray ( ) . map ( action = > ( { requiredPolicy : action.permission } ) ) ,
) . length > 0 ;
) . length > 0 ;
// Watch actionsColumnWidth input
effect ( ( ) = > {
const width = this . actionsColumnWidthInput ( ) ;
this . _actionsColumnWidth . set ( width ? Number ( width ) : undefined ) ;
} ) ;
// Watch data input changes
effect ( ( ) = > {
const dataValue = this . dataInput ( ) ;
if ( ! dataValue ) return ;
if ( dataValue . length < 1 ) {
this . list ( ) . totalCount = this . recordsTotal ( ) ;
}
this . _data . set ( dataValue . map ( ( record : any , index : number ) = > {
this . propList . forEach ( prop = > {
const propData = { getInjected : this.getInjected , record , index } as ReadonlyPropData ;
const value = this . getContent ( prop . value , propData ) ;
const propKey = ` _ ${ prop . value . name } ` ;
record [ propKey ] = {
visible : prop.value.visible ( propData ) ,
value ,
} ;
if ( prop . value . component ) {
record [ propKey ] . injector = Injector . create ( {
providers : [
{
provide : PROP_DATA_STREAM ,
useValue : value ,
} ,
{
provide : ROW_RECORD ,
useValue : record ,
} ,
] ,
parent : this. # injector ,
} ) ;
record [ propKey ] . component = prop . value . component ;
}
} ) ;
return record ;
} ) ) ;
} ) ;
}
}
private getIcon ( value : boolean ) {
private getIcon ( value : boolean ) {
@ -201,7 +244,7 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
return key ;
return key ;
}
}
getContent ( prop : EntityProp < R > , data : PropData ) : Observable < string > {
getContent ( prop : EntityProp < R > , data : Readonly PropData) : Observable < string > {
return prop . valueResolver ( data ) . pipe (
return prop . valueResolver ( data ) . pipe (
map ( value = > {
map ( value = > {
switch ( prop . type ) {
switch ( prop . type ) {
@ -217,45 +260,6 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
) ;
) ;
}
}
ngOnChanges ( { data } : SimpleChanges ) {
if ( ! data ? . currentValue ) return ;
if ( data . currentValue . length < 1 ) {
this . list ( ) . totalCount = this . recordsTotal ( ) ;
}
this . data = data . currentValue . map ( ( record : any , index : number ) = > {
this . propList . forEach ( prop = > {
const propData = { getInjected : this.getInjected , record , index } as any ;
const value = this . getContent ( prop . value , propData ) ;
const propKey = ` _ ${ prop . value . name } ` ;
record [ propKey ] = {
visible : prop.value.visible ( propData ) ,
value ,
} ;
if ( prop . value . component ) {
record [ propKey ] . injector = Injector . create ( {
providers : [
{
provide : PROP_DATA_STREAM ,
useValue : value ,
} ,
{
provide : ROW_RECORD ,
useValue : record ,
} ,
] ,
parent : this. # injector ,
} ) ;
record [ propKey ] . component = prop . value . component ;
}
} ) ;
return record ;
} ) ;
}
isVisibleActions ( rowData : any ) : boolean {
isVisibleActions ( rowData : any ) : boolean {
const actions = this . actionList . toArray ( ) ;
const actions = this . actionList . toArray ( ) ;
const visibleActions = actions . filter ( action = > {
const visibleActions = actions . filter ( action = > {
@ -278,9 +282,9 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
return visibleActions . length > 0 ;
return visibleActions . length > 0 ;
}
}
onSelect ( { selected } ) {
onSelect ( { selected } : { selected : any [ ] } ) {
const selectedValue = this . selected ( ) ;
const selectedValue = this . selected ( ) ;
this . selected ( ) . splice ( 0 , selectedValue . length ) ;
selectedValue . splice ( 0 , selectedValue . length ) ;
selectedValue . push ( . . . selected ) ;
selectedValue . push ( . . . selected ) ;
this . selectionChange . emit ( selected ) ;
this . selectionChange . emit ( selected ) ;
}
}
@ -330,7 +334,7 @@ export class ExtensibleTableComponent<R = any> implements OnChanges, AfterViewIn
ngAfterViewInit ( ) : void {
ngAfterViewInit ( ) : void {
if ( ! this . infiniteScroll ( ) ) {
if ( ! this . infiniteScroll ( ) ) {
this . list ( ) ? . requestStatus $ ? . pipe ( filter ( status = > status === 'loading' ) ) . subscribe ( ( ) = > {
this . list ( ) ? . requestStatus $ ? . pipe ( filter ( status = > status === 'loading' ) ) . subscribe ( ( ) = > {
this . data = [ ] ;
this . _data . set ( [ ] ) ;
this . cdr . markForCheck ( ) ;
this . cdr . markForCheck ( ) ;
} ) ;
} ) ;
}
}