Compare commits

..

1 Commits

Author SHA1 Message Date
shamoon ab7a546366 Performance: optimize dropdown filtering 2026-07-07 11:20:54 -07:00
3 changed files with 80 additions and 10 deletions
@@ -61,12 +61,15 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
writeValue(newValue: number[]): void {
this.value = newValue
}
registerOnChange(fn: any): void {
this.onChange = fn
}
registerOnTouched(fn: any): void {
this.onTouched = fn
}
setDisabledState?(isDisabled: boolean): void {
this.disabled = isDisabled
}
@@ -74,6 +77,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
ngOnInit(): void {
this.tagService.listAll().subscribe((result) => {
this.tags = result.results
this.tagSearchTextCache.clear()
})
}
@@ -113,15 +117,12 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
tags: Tag[] = []
private readonly tagSearchTextCache = new Map<number, string>()
public createTagRef: (name) => void
public searchFn = (term: string, tag: Tag): boolean =>
matchesSearchText(
[this.getParentChain(tag?.id).map((parent) => parent.name), tag?.name]
.flat()
.join(' '),
term
)
matchesSearchText(this.getTagSearchText(tag), term)
getTag(id: number) {
if (this.tags) {
@@ -152,8 +153,8 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
private removeChildren(tagIDs: number[], tag: Tag) {
if (tag.children?.length) {
const childIDs = tag.children.map((child) => child.id)
tagIDs = tagIDs.filter((id) => !childIDs.includes(id))
const childIDs = new Set(tag.children.map((child) => child.id))
tagIDs = tagIDs.filter((id) => !childIDs.has(id))
for (const child of tag.children) {
tagIDs = this.removeChildren(tagIDs, child)
}
@@ -188,6 +189,7 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
tap((newTag) => {
this.tagService.listAll().subscribe((tags) => {
this.tags = tags.results
this.tagSearchTextCache.clear()
add && this.addTag(newTag.id)
})
})
@@ -238,4 +240,22 @@ export class TagsComponent implements OnInit, ControlValueAccessor {
}
return chain
}
private getTagSearchText(tag: Tag): string {
if (!tag) {
return ''
}
const cachedSearchText = this.tagSearchTextCache.get(tag.id)
if (cachedSearchText !== undefined) {
return cachedSearchText
}
const searchText = [
...this.getParentChain(tag.id).map((parent) => parent.name),
tag.name,
].join(' ')
this.tagSearchTextCache.set(tag.id, searchText)
return searchText
}
}
+19
View File
@@ -46,4 +46,23 @@ describe('FilterPipe', () => {
itemsReturned = pipe.transform(items, 'slug', 'slug')
expect(itemsReturned).toEqual([items[0]])
})
it('should filter matchingmodel items by normalized independent terms', () => {
const pipe = new FilterPipe()
const items: MatchingModel[] = [
{
id: 1,
name: 'Financ\u00e9 Taxes 2026',
slug: 'finance-taxes',
},
{
id: 2,
name: 'Receipt Archive 2026',
slug: 'receipt-archive',
},
]
expect(pipe.transform(items, 'finance 26')).toEqual([items[0]])
expect(pipe.transform(items, 'finance receipt')).toEqual([])
})
})
+33 -2
View File
@@ -8,8 +8,39 @@ export type SearchTextValue =
| null
| undefined
const normalizedStringCache = new Map<string, string>()
const searchTermsCache = new Map<string, string[]>()
function normalizeString(value: string): string {
const cachedValue = normalizedStringCache.get(value)
if (cachedValue !== undefined) {
return cachedValue
}
const normalizedValue = normalizeSync(value).toLocaleLowerCase()
normalizedStringCache.set(value, normalizedValue)
return normalizedValue
}
export function normalizeSearchText(value: SearchTextValue): string {
return normalizeSync(String(value ?? '')).toLocaleLowerCase()
return normalizeString(String(value ?? ''))
}
function getSearchTerms(searchText: SearchTextValue): string[] {
const normalizedSearchText = normalizeSearchText(searchText).trim()
if (!normalizedSearchText) {
return []
}
const cachedTerms = searchTermsCache.get(normalizedSearchText)
if (cachedTerms !== undefined) {
return cachedTerms
}
const searchTerms = normalizedSearchText.split(/\s+/)
searchTermsCache.set(normalizedSearchText, searchTerms)
return searchTerms
}
export function matchesSearchText(
@@ -17,7 +48,7 @@ export function matchesSearchText(
searchText: SearchTextValue
): boolean {
const normalizedValue = normalizeSearchText(value)
const searchTerms = normalizeSearchText(searchText).trim().split(/\s+/)
const searchTerms = getSearchTerms(searchText)
return searchTerms.every((term) => normalizedValue.includes(term))
}