Browse Source

Task/improve type safety in tags selector component (#6497)

* Improve type safety
pull/6089/merge
Kenrick Tandrian 1 week ago
committed by GitHub
parent
commit
7c20bfff92
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 7
      libs/ui/src/lib/tags-selector/interfaces/interfaces.ts
  2. 2
      libs/ui/src/lib/tags-selector/tags-selector.component.html
  3. 49
      libs/ui/src/lib/tags-selector/tags-selector.component.ts

7
libs/ui/src/lib/tags-selector/interfaces/interfaces.ts

@ -0,0 +1,7 @@
import { Tag } from '@prisma/client';
export interface NewTag extends Omit<Tag, 'id'> {
id: undefined;
}
export type SelectedTag = NewTag | Tag;

2
libs/ui/src/lib/tags-selector/tags-selector.component.html

@ -2,7 +2,7 @@
<div class="col"> <div class="col">
@if (readonly) { @if (readonly) {
<div class="h5" i18n>Tags</div> <div class="h5" i18n>Tags</div>
@if (tags?.length > 0) { @if (tags && tags.length > 0) {
<mat-chip-listbox> <mat-chip-listbox>
@for (tag of tags; track tag) { @for (tag of tags; track tag) {
<mat-chip-option disabled>{{ tag.name }}</mat-chip-option> <mat-chip-option disabled>{{ tag.name }}</mat-chip-option>

49
libs/ui/src/lib/tags-selector/tags-selector.component.ts

@ -7,11 +7,11 @@ import {
ElementRef, ElementRef,
Input, Input,
OnChanges, OnChanges,
OnDestroy,
OnInit, OnInit,
signal, signal,
ViewChild viewChild
} from '@angular/core'; } from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { import {
ControlValueAccessor, ControlValueAccessor,
FormControl, FormControl,
@ -30,7 +30,9 @@ import { IonIcon } from '@ionic/angular/standalone';
import { Tag } from '@prisma/client'; import { Tag } from '@prisma/client';
import { addIcons } from 'ionicons'; import { addIcons } from 'ionicons';
import { addCircleOutline, closeOutline } from 'ionicons/icons'; import { addCircleOutline, closeOutline } from 'ionicons/icons';
import { BehaviorSubject, Subject, takeUntil } from 'rxjs'; import { BehaviorSubject, Subject } from 'rxjs';
import { SelectedTag } from './interfaces/interfaces';
@Component({ @Component({
changeDetection: ChangeDetectionStrategy.OnPush, changeDetection: ChangeDetectionStrategy.OnPush,
@ -57,27 +59,28 @@ import { BehaviorSubject, Subject, takeUntil } from 'rxjs';
templateUrl: 'tags-selector.component.html' templateUrl: 'tags-selector.component.html'
}) })
export class GfTagsSelectorComponent export class GfTagsSelectorComponent
implements ControlValueAccessor, OnChanges, OnDestroy, OnInit implements ControlValueAccessor, OnChanges, OnInit
{ {
@Input() hasPermissionToCreateTag = false; @Input() hasPermissionToCreateTag = false;
@Input() readonly = false; @Input() readonly = false;
@Input() tags: Tag[]; @Input() tags: SelectedTag[];
@Input() tagsAvailable: Tag[]; @Input() tagsAvailable: SelectedTag[];
@ViewChild('tagInput') tagInput: ElementRef<HTMLInputElement>;
public filteredOptions: Subject<Tag[]> = new BehaviorSubject([]); public readonly filteredOptions: Subject<SelectedTag[]> = new BehaviorSubject(
[]
);
public readonly separatorKeysCodes: number[] = [COMMA, ENTER]; public readonly separatorKeysCodes: number[] = [COMMA, ENTER];
public readonly tagInputControl = new FormControl(''); public readonly tagInputControl = new FormControl('');
public readonly tagsSelected = signal<Tag[]>([]); public readonly tagsSelected = signal<SelectedTag[]>([]);
private unsubscribeSubject = new Subject<void>(); private readonly tagInput =
viewChild.required<ElementRef<HTMLInputElement>>('tagInput');
public constructor() { public constructor() {
this.tagInputControl.valueChanges this.tagInputControl.valueChanges
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntilDestroyed())
.subscribe((value) => { .subscribe((value) => {
this.filteredOptions.next(this.filterTags(value)); this.filteredOptions.next(this.filterTags(value ?? ''));
}); });
addIcons({ addCircleOutline, closeOutline }); addIcons({ addCircleOutline, closeOutline });
@ -106,6 +109,7 @@ export class GfTagsSelectorComponent
}; };
} }
if (tag) {
this.tagsSelected.update((tags) => { this.tagsSelected.update((tags) => {
return [...(tags ?? []), tag]; return [...(tags ?? []), tag];
}); });
@ -113,8 +117,10 @@ export class GfTagsSelectorComponent
const newTags = this.tagsSelected(); const newTags = this.tagsSelected();
this.onChange(newTags); this.onChange(newTags);
this.onTouched(); this.onTouched();
this.tagInput.nativeElement.value = ''; }
this.tagInputControl.setValue(undefined);
this.tagInput().nativeElement.value = '';
this.tagInputControl.setValue(null);
} }
public onRemoveTag(tag: Tag) { public onRemoveTag(tag: Tag) {
@ -130,7 +136,7 @@ export class GfTagsSelectorComponent
this.updateFilters(); this.updateFilters();
} }
public registerOnChange(fn: (value: Tag[]) => void) { public registerOnChange(fn: (value: SelectedTag[]) => void) {
this.onChange = fn; this.onChange = fn;
} }
@ -146,17 +152,12 @@ export class GfTagsSelectorComponent
} }
} }
public writeValue(value: Tag[]) { public writeValue(value: SelectedTag[]) {
this.tagsSelected.set(value || []); this.tagsSelected.set(value || []);
this.updateFilters(); this.updateFilters();
} }
public ngOnDestroy() { private filterTags(query: string = ''): SelectedTag[] {
this.unsubscribeSubject.next();
this.unsubscribeSubject.complete();
}
private filterTags(query: string = ''): Tag[] {
const tags = this.tagsSelected() ?? []; const tags = this.tagsSelected() ?? [];
const tagIds = tags.map(({ id }) => { const tagIds = tags.map(({ id }) => {
return id; return id;
@ -170,7 +171,7 @@ export class GfTagsSelectorComponent
} }
// eslint-disable-next-line @typescript-eslint/no-unused-vars // eslint-disable-next-line @typescript-eslint/no-unused-vars
private onChange = (_value: Tag[]): void => { private onChange = (_value: SelectedTag[]): void => {
// ControlValueAccessor onChange callback // ControlValueAccessor onChange callback
}; };

Loading…
Cancel
Save