Browse Source

Feature/extend GfSymbolAutocompleteComponent by default options (#4563)

* Extend GfSymbolAutocompleteComponent by default options

* Update changelog
pull/4846/head
csehatt741 4 weeks ago
committed by GitHub
parent
commit
b5abdaae07
No known key found for this signature in database GPG Key ID: B5690EEEBB952194
  1. 1
      CHANGELOG.md
  2. 18
      libs/ui/src/lib/mocks/httpClient.mock.ts
  3. 2
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html
  4. 115
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts
  5. 38
      libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

1
CHANGELOG.md

@ -14,6 +14,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Changed ### Changed
- Extended the symbol search component by default options
- Improved the language localization for Spanish (`es`) - Improved the language localization for Spanish (`es`)
- Upgraded `ng-extract-i18n-merge` from version `2.15.0` to `2.15.1` - Upgraded `ng-extract-i18n-merge` from version `2.15.0` to `2.15.1`
- Upgraded `Nx` from version `20.8.1` to `21.1.2` - Upgraded `Nx` from version `20.8.1` to `21.1.2`

18
libs/ui/src/lib/mocks/httpClient.mock.ts

@ -0,0 +1,18 @@
import { Observable } from 'rxjs';
export class HttpClientMock {
public constructor(private mockResponses: Map<string, any>) {}
public get<TResponse>(url: string, options?: any): Observable<TResponse> {
if (this.mockResponses.has(url) && options) {
return new Observable<TResponse>((subscriber) => {
subscriber.next(this.mockResponses.get(url));
subscriber.complete();
});
}
return new Observable<TResponse>((subscriber) => {
subscriber.error(new Error(`No mock data for URL: ${url}`));
});
}
}

2
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.html

@ -12,7 +12,7 @@
(optionSelected)="onUpdateSymbol($event)" (optionSelected)="onUpdateSymbol($event)"
> >
@if (!isLoading) { @if (!isLoading) {
@for (lookupItem of filteredLookupItems; track lookupItem) { @for (lookupItem of lookupItems; track lookupItem) {
<mat-option <mat-option
class="line-height-1" class="line-height-1"
[disabled]="lookupItem.dataProviderInfo.isPremium" [disabled]="lookupItem.dataProviderInfo.isPremium"

115
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.stories.ts

@ -0,0 +1,115 @@
import { LookupItem } from '@ghostfolio/common/interfaces';
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { importProvidersFrom } from '@angular/core';
import {
FormControl,
FormsModule,
NgControl,
ReactiveFormsModule
} from '@angular/forms';
import { provideNoopAnimations } from '@angular/platform-browser/animations';
import { applicationConfig, Meta, StoryObj } from '@storybook/angular';
import { HttpClientMock } from '../mocks/httpClient.mock';
import { GfSymbolAutocompleteComponent } from './symbol-autocomplete.component';
const DEFAULT_OPTIONS: LookupItem[] = [
{
assetClass: 'COMMODITY',
assetSubClass: 'ETF',
currency: 'USD',
dataProviderInfo: {
dataSource: 'YAHOO',
isPremium: false
},
dataSource: null,
name: 'Default 1',
symbol: 'D1'
},
{
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
currency: 'USD',
dataProviderInfo: {
dataSource: 'YAHOO',
isPremium: false
},
dataSource: null,
name: 'Default 2',
symbol: 'D2'
}
];
const FILTERED_OPTIONS: LookupItem[] = [
{
assetClass: 'COMMODITY',
assetSubClass: 'ETF',
currency: 'USD',
dataProviderInfo: {
dataSource: 'YAHOO',
isPremium: false
},
dataSource: null,
name: 'Autocomplete 1',
symbol: 'A1'
},
{
assetClass: 'EQUITY',
assetSubClass: 'STOCK',
currency: 'USD',
dataProviderInfo: {
dataSource: 'YAHOO',
isPremium: false
},
dataSource: null,
name: 'Autocomplete 2',
symbol: 'A2'
}
];
export default {
title: 'Symbol Autocomplete',
component: GfSymbolAutocompleteComponent,
decorators: [
applicationConfig({
providers: [
provideNoopAnimations(),
importProvidersFrom(CommonModule, FormsModule, ReactiveFormsModule),
{
provide: NgControl,
useValue: {
control: new FormControl(),
valueAccessor: null
}
},
{
provide: HttpClient,
useValue: new HttpClientMock(
new Map([
[
'/api/v1/symbol/lookup',
{
items: FILTERED_OPTIONS
}
]
])
)
}
]
})
]
} as Meta<GfSymbolAutocompleteComponent>;
type Story = StoryObj<GfSymbolAutocompleteComponent>;
export const Default: Story = {
args: {}
};
export const WithDefaultItems: Story = {
args: {
defaultLookupItems: DEFAULT_OPTIONS
}
};

38
libs/ui/src/lib/symbol-autocomplete/symbol-autocomplete.component.ts

@ -76,16 +76,17 @@ export class GfSymbolAutocompleteComponent
extends AbstractMatFormField<LookupItem> extends AbstractMatFormField<LookupItem>
implements OnInit, OnDestroy implements OnInit, OnDestroy
{ {
@Input() private includeIndices = false; @Input() public defaultLookupItems: LookupItem[] = [];
@Input() public isLoading = false; @Input() public isLoading = false;
@ViewChild(MatInput) private input: MatInput;
@ViewChild('symbolAutocomplete') public symbolAutocomplete: MatAutocomplete; @ViewChild('symbolAutocomplete') public symbolAutocomplete: MatAutocomplete;
@Input() private includeIndices = false;
@ViewChild(MatInput) private input: MatInput;
public control = new FormControl(); public control = new FormControl();
public filteredLookupItems: (LookupItem & { assetSubClassString: string })[] = public lookupItems: (LookupItem & { assetSubClassString: string })[] = [];
[];
private unsubscribeSubject = new Subject<void>(); private unsubscribeSubject = new Subject<void>();
@ -106,6 +107,10 @@ export class GfSymbolAutocompleteComponent
this.control.disable(); this.control.disable();
} }
if (this.defaultLookupItems?.length) {
this.showDefaultOptions();
}
this.control.valueChanges this.control.valueChanges
.pipe(takeUntil(this.unsubscribeSubject)) .pipe(takeUntil(this.unsubscribeSubject))
.subscribe(() => { .subscribe(() => {
@ -117,7 +122,13 @@ export class GfSymbolAutocompleteComponent
this.control.valueChanges this.control.valueChanges
.pipe( .pipe(
filter((query) => { filter((query) => {
return isString(query) && query.length > 1; if (query.length === 0) {
this.showDefaultOptions();
return false;
}
return isString(query);
}), }),
tap(() => { tap(() => {
this.isLoading = true; this.isLoading = true;
@ -135,7 +146,7 @@ export class GfSymbolAutocompleteComponent
}) })
) )
.subscribe((filteredLookupItems) => { .subscribe((filteredLookupItems) => {
this.filteredLookupItems = filteredLookupItems.map((lookupItem) => { this.lookupItems = filteredLookupItems.map((lookupItem) => {
return { return {
...lookupItem, ...lookupItem,
assetSubClassString: translate(lookupItem.assetSubClass) assetSubClassString: translate(lookupItem.assetSubClass)
@ -161,7 +172,7 @@ export class GfSymbolAutocompleteComponent
} }
public isValueInOptions(value: string) { public isValueInOptions(value: string) {
return this.filteredLookupItems.some((item) => { return this.lookupItems.some((item) => {
return item.symbol === value; return item.symbol === value;
}); });
} }
@ -193,6 +204,17 @@ export class GfSymbolAutocompleteComponent
this.unsubscribeSubject.complete(); this.unsubscribeSubject.complete();
} }
private showDefaultOptions() {
this.lookupItems = this.defaultLookupItems.map((lookupItem) => {
return {
...lookupItem,
assetSubClassString: translate(lookupItem.assetSubClass)
};
});
this.changeDetectorRef.markForCheck();
}
private validateRequired() { private validateRequired() {
const requiredCheck = super.required const requiredCheck = super.required
? !super.value?.dataSource || !super.value?.symbol ? !super.value?.dataSource || !super.value?.symbol

Loading…
Cancel
Save