Search code examples
javascripttypescriptsearchaureliacomputed-properties

aurelia search filtering using a computed is causing issues when rendering to view


I have a search function that retrieves objects

this.searchForObjectByName = () => {
            this.server.get('objects/?searchTerm=' + self.searchTerm)
                .then(groupObjects=> 
                {
                    this.searchResults = groupObjects;
                });
        }

I then set the result of this to a property called searchResults, which are a groups of objects. I now want to perform some filtering base on the results. Say I have some groups of objects that look like:

{
    id: 1, 
    name: 'group 1',
    objects: [
        {
          id: 1,
          name: 'object name 1',
          categories: ['Cat A', 'Cat B']
        },
        {
          id: 2,
          name: 'object name 2',
          categories: ['Cat A', 'Cat D']
        },
        {
          id: 3,
          name: 'object name 3',
          categories: ['Cat C', 'Cat D']
        }
    ]
},
{
    id: 2, 
    name: 'group 2',
    objects: [
        {
          id: 4,
          name: 'object name 4',
          categories: ['Cat A', 'Cat F']
        },
        {
          id: 5,
          name: 'object name 5',
          categories: ['Cat C', 'Cat D']
        }
    ]
}

I would like to then filter to only show Cat A objects. I have a computed that filters the search results based on selected categories:

get filteredSearchResults(): any[] {
    var filteredGroups = this.searchResults.map(res => {
        let filteredGroup = new Group;
        filteredGroup = res.id,
        filteredGroup = res.name;
        filteredGroup = res.objects.filter(o => this.filteredCategories.some(fc => o.categories.some(oc => oc == fc)));
        return filteredGroup;
    });

    filteredGroups = filteredGroups.filter(fg => fg.objects.length > 0);
    return filteredGroups;
}

This filters the results fine and I can select different categories and it filters correctly. However it jumps all over the place in the UI, the results are constantly updating due to using a computed to filter the results. I can't use the computedfrom attribute because its based on the filtered categories I've selected (that are an array) and the results returning from the server (also an array). Has anyone else experienced this issue before? Everything I try causes jitter and I'm not sure how I can do this differently.

I could trigger events when selected categories change or when data is returned from the server, I could then recalculate the filtered results. This would work but is hacky and would prefer to be able to do this in a computed


Solution

  • Without computedFrom, your property getter is being polled, which is why your interface is jumping around. I'm not familiar with .some, but I'm guessing order is not guaranteed. While you could simply order your search results to work around this, it would be pretty inefficient to be filtering and sorting an array every 5 seconds.

    Using a collection observer your filter code will only be executed when your interface filters actually change. I haven't actually tested this code

    import { BindingEngine, Disposable, bindable } from 'aurelia-framework';
    
    export class MyPage
    {
        @bindable searchTerm: string;
        filteredSearchResults: any[];
        subscription: Disposable;
    
        constructor(private bindingEngine: BindingEngine)
        {
        }
    
        attached()
        {
            this.subscription = this.bindingEngine.collectionObserver(this.filteredCategories).subscribe(this.filtersChanged);
        }
    
        detached()
        {
            this.subscription.dispose();
        }
    
        searchTermChanged(newValue: string, oldValue: string)
        {
            this.find();
        }
    
        find()
        {
             this.server.get('objects/?searchTerm=' + this.searchTerm)
                    .then(groupObjects=> 
                    {
                        this.searchResults = groupObjects;
                        this.update();
                    });
        }
    
        update()
        {
            // your original filtering logic unchanged
            var filteredGroups = this.searchResults.map(res =>
            {
                let filteredGroup = new Group;
                filteredGroup = res.id,
                filteredGroup = res.name;
                filteredGroup = res.objects.filter(o => this.filteredCategories.some(fc => o.categories.some(oc => oc == fc)));
                return filteredGroup;
            });
    
            filteredGroups = filteredGroups.filter(fg => fg.objects.length > 0);
    
            // set value instead of using a computed property
            this.filteredSearchResults = filteredGroups;
        }
    
        filtersChanged(splices)
        {
            this.update(); // you could remove this method and bind the array observable directly to update()
        }
    }