4

I would like to know how the setFilter method works for the EntityCollectionService in @ngrx/data. The documentation hints at how it is used, but there is no example showing the actual setFilter(pattern: any) function being used. Since the argument can be of type any, I cannot really infer what should be done here.

Basically, I have a list of objects in the data store using the @ngrx/data module. I would like to define a filter so that I can subscribe to the filteredEntities$ observable of the EntityCollectionService. I can successfully subscribe to the entities$ observable and receive the full unfiltered list. Previously, I was doing the filtering outside of the EntityCollectionService, but I would like to utilize the built-in filtering mechanism.


export class MyComponent implements OnInit {
  filteredProjects$: Observable<Project[]>;
  typeFilterOptions: FilterOption[];
  stageFilterOptions: FilterOption[];

  constructor(private projectService: ProjectEntityService, ptivate metadataService: MetadataService) {}

  ngOnInit() {
    this.typeFilterOptions = this.metadataService.getProjectTypes();
    this.stageFilterOptions = this.metadataService.getProjectStages();

    this.filteredProjects$ = this.projectService.filteredEntities$;
  }

  onFilterChange() {
    typeFilter = typeFilterOptions.filter(option => option.isChecked).map(option.name);
    stageFilter = stageFilterOptions.filter(option => option.isChecked).map(option.name);

    this.projectService.setFilter(project => {
      return (typeFilter.indexOf(project.type) >= 0) &&
             (stageFilter.indexOf(project.stage) >= 0); 
    }
  }
}

The above code is my best approach at trying to set the filter correctly. Obviously, that is not working as I expected it would. When setting the filter to a filter function nothing changes even though I can see the set filter action firing as expected. The entities are still not being filtered at that point. The argument being label as pattern: any make me think that it should be something other than a function, but again I cannot infer off of the documentation what it is expecting.

bkelley
  • 112
  • 9

2 Answers2

4

Ok so digging into the source code I was able to figure out how to use the filter on an ngrx/data Entity Service.

The piece I was missing was defining the filter function in the Entity Service metadata configuration (see docs here):

app.module.ts


const entityMetadata: EntityMetadataMap = {
  Project: {
    //pattern can be any object you want it to be.  This is the same argument used in setFilter(pattern: any)
    filterFn: (entities: Project[], pattern: {types: string[], stages: string[]}) => {
      return entitites.filter(entity => {
       return (pattern.types.indexOf(entity.type) >= 0) &&
              (pattern.stages.indexOf(entity.stage) >= 0)
      });
    }
  }
};

@NgModule({
    ...
})
export class AppModule {

    constructor(private eds: EntityDefinitionService) {

    eds.registerMetadataMap(entityMetadata);
  }
}

then in the component all you need to do is create the filter object and use it as the argument to setFilter on the Entity Service:

my.component.ts

export class MyComponent implements OnInit {
  filteredProjects$: Observable<Project[]>;
  typeFilterOptions: FilterOption[];
  stageFilterOptions: FilterOption[];

  constructor(private projectService: ProjectEntityService, private metadataService: MetadataService) {}

  ngOnInit() {
    this.typeFilterOptions = this.metadataService.getProjectTypes();
    this.stageFilterOptions = this.metadataService.getProjectStages();

    this.filteredProjects$ = this.projectService.filteredEntities$;
  }

  onFilterChange() {
    typeFilter = typeFilterOptions.filter(option => option.isChecked).map(option.name);
    stageFilter = stageFilterOptions.filter(option => option.isChecked).map(option.name);

    this.projectService.setFilter({
      types: typeFilter,
      stages: stageFilter
    });
  }
}

At this point anything in your template subscribed to the filteredProjects$ observable will get the update filtered entities when setFilter is called. For example:

my.component.html

...
<app-project-list [projects]="filteredProjects$ | async"></app-project-list>
...
bkelley
  • 112
  • 9
4

An easy way is just to use your entity's model, say User

export const entityMetadata: EntityMetadataMap = {
  User: {
    filterFn: (entities: User[], { email, name }: Partial<User>) =>
      entities
        .filter((user) => (email? -1 < user.email.indexOf(email) : true))
        .filter((user) => (name ? -1 < user.name.indexOf(name) : true))
  }
};

and then in your component

constructor(private collectionService: UserCollectionService) {
  ...
}

filter() {
  this.collectionService.setFilter({ name: 'jan' });
  // or this.collectionService.setFilter({ name: 'jan', email: '@github.com' });
}
Jan
  • 320
  • 2
  • 6