Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[feat] Unique ids as inputs for components #3720

Open
Michael-Ziluck opened this issue May 7, 2020 · 3 comments
Open

[feat] Unique ids as inputs for components #3720

Michael-Ziluck opened this issue May 7, 2020 · 3 comments

Comments

@Michael-Ziluck
Copy link

When trying to things dynamically, we may not be able to have a single typeahead property in the component for each input. For instance, if you want to have a bunch of fields within an *ngFor, you wouldn't be able to give each their own template ref. Instead, they would be referenced by a QueryList like so:

@ViewChildren(NgbTypeahead) typeaheadChildren: QueryList<NgbTypeahead>;

If I wanted to also dynamically create a function that uses a specific NgbTypeahead, like below (see the comment in the code with an arrow), I would not be able to uniquely pick one of them out of a QueryList:

export type Supplier<T> = () => T;
export type TypeaheadFocusSubject = Subject<{ input: string, value: string }>;
export type LookupFunction = (searchText: string) => Observable<any>;
export type TypeaheadFunction = (text$: Observable<string>) => Observable<any>;

export function createTypeaheadFunction(
    filterId: string,
    focus$: TypeaheadFocusSubject,
    typeahead: Supplier<NgbTypeahead>,
    lookupFunction: LookupFunction,
): TypeaheadFunction {
    return (text$: Observable<string>) => {
        const debouncedText$: Observable<string> = text$.pipe(
            debounceTime(500),
            distinctUntilChanged(),
        );
        const inputFocus$: Observable<string> = focus$.pipe(
            filter(event => event.input === filterId),
            filter(() => !typeahead().isPopupOpen()), // <-- THIS LINE
            map(event => event.value),
        );

        return merge(debouncedText$, inputFocus$).pipe(switchMap(lookupFunction));
    };
}

Right now, a very hacky workaround to get this functionality to work would be something like this, where fieldConfigs is an array of values describing how the dynamic content will work:

for (const field of fieldConfigs) {
    if (field.filterTypeahead) {
        const functionSupplier: Supplier<TypeaheadFunction> = () => field.internalTypeahead;
        const supplier: Supplier<NgbTypeahead> = () => this.typeaheadChildren.find(
            typeahead => typeahead.ngbTypeahead === functionSupplier(),
        );
        field.internalTypeahead = createTypeaheadFunction(
            field.id,
            this.focus$,
            supplier,
            field.filterTypeahead
        );
    }
}

If there was a field named id on NgbTypeahead (and other components), you would be able to do this instead for the above example which is must easier to follow:

for (const field of fieldConfigs) {
    if (field.filterTypeahead) {
        const supplier: Supplier<NgbTypeahead> = () => this.typeaheadChildren.find(
            typeahead => typeahead.id === field.id,
        );
        field.internalTypeahead = createTypeaheadFunction(
            field.id,
            this.focus$,
            supplier,
            field.filterTypeahead
        );
    }
}

Let me know if you need me to provide a StackBlitz, but as this is a feature request I feel like it's fine to omit one.

Versions of Angular, ng-bootstrap and Bootstrap:

Angular: Latest

ng-bootstrap: Latest

Bootstrap: Latest

@benouat
Copy link
Member

benouat commented May 14, 2020

Hey @Michael-Ziluck !

Would mind detailing a bit more the use case of generating several typeahead components in a for loop ?

Usually, at least for me, regrouping components to be generated into a loop is because these components are related to each other by design.

It seems here that I am missing the point of what you're trying to achieve. And your code snippets did not help me understand it better. Sorry ...

@Michael-Ziluck
Copy link
Author

Yeah, for sure!

Basically I'm creating a data table component that is created by passing in configuration for each column. The column config contains things like this:

export interface ColumnConfig {
    filterable: boolean;
    filterType: 'datepicker' | 'typeahead' | 'dropdown';
    filterTypeahead: LookupFunction
}

I would like that LookupFunction type to be as barebones as possible. Basically it just makes a request and that's it. That works fine, except for when you try to construct the debounced Observable like in my code in the original description. In there, I need to be able to access the individual instance of Typeahead.

Since I am generating content dynamically in an *ngFor over the column configs, I am not able to have a single field. Because of that, I need to use the @ViewChildren annotation and go through each of them.

Right now, there's no way to uniquely identifying it without the semi-hacky example I also put above.

@benouat
Copy link
Member

benouat commented May 20, 2020

Sorry, it is still don't get entirely the global picture, and it's not clear to me how having ids would help ....

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants