Skip to content

Commit

Permalink
[Feature] Custom no entries found element (#382)
Browse files Browse the repository at this point in the history
* add ngxMatSelectNoEntriesFound directive

* change ngx-mat-select-search to use ngxMatSelectNoEntriesFound directive

* add custom no entries found element example

* rename no entries found example component

* export MatSelectNoEntriesFoundDirective in NgxMatSelectSearchModule

* fix typo

* format mat-select-search.component.html

* move custom-no-entries-found-example.component

* add README.md entry for customize no entries found element

* adjust custom no entries found example

Co-authored-by: Esteban Gehring <esteban.gehring@bithost.ch>
  • Loading branch information
ruekart and macjohnny committed Jun 14, 2022
1 parent a6c3e22 commit 7a804d4
Show file tree
Hide file tree
Showing 10 changed files with 160 additions and 3 deletions.
13 changes: 13 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,19 @@ In order to customize the search icon, add the `ngxMatSelectSearchClear` to your
```
If just the icon should be changed the inputs `closeIcon` and `closeSvgIcon` can be used.

#### Customize no entries found element
In order to customize the no entries found element, add the `ngxMatSelectNoEntriesFound` to your custom item (a `mat-icon, span, button` or any other element) and place it inside the `ngx-mat-select-search` component:
```html
<ngx-mat-select-search>
<span ngxMatSelectNoEntriesFound>
No entries found
<button mat-button color="primary">
Add <mat-icon>add</mat-icon>
</button>
</span>
</ngx-mat-select-search>
```

#### Custom content
Custom content with the CSS class `mat-select-search-custom-header-content` can be transcluded as follows:
```html
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ <h2>Examples</h2>

<app-custom-clear-icon-example></app-custom-clear-icon-example>

<app-custom-no-entries-found-example></app-custom-no-entries-found-example>

<app-option-groups-example></app-option-groups-example>

<app-server-side-search-example></app-server-side-search-example>
Expand Down
2 changes: 2 additions & 0 deletions src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {
} from './examples/06-multiple-selection-select-all-example/multiple-selection-select-all-example.component';
import { TooltipSelectAllExampleComponent } from './examples/07-tooltip-select-all-example/tooltip-select-all-example.component';
import { InfiniteScrollExampleComponent } from './examples/08-infinite-scroll-example/infinite-scroll-example.component';
import { CustomNoEntriesFoundExampleComponent } from './examples/09-custom-no-entries-found-example/custom-no-entries-found-example.component';
import { MatSlideToggleModule } from '@angular/material/slide-toggle';


Expand Down Expand Up @@ -56,6 +57,7 @@ export class MaterialModule {}
SingleSelectionExampleComponent,
MultipleSelectionExampleComponent,
CustomClearIconExampleComponent,
CustomNoEntriesFoundExampleComponent,
OptionGroupsExampleComponent,
ServerSideSearchExampleComponent,
MultipleSelectionSelectAllExampleComponent,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<h3>Single selection with custom no entries found element</h3>
<p>
<mat-form-field>
<mat-select [formControl]="bankCtrl" placeholder="Bank" #singleSelect>
<mat-option>
<ngx-mat-select-search *ngIf="true" [formControl]="bankFilterCtrl" [preventHomeEndKeyPropagation]="true">
<span ngxMatSelectNoEntriesFound>
No entries found
<button mat-button color="primary">
Add <mat-icon>add</mat-icon>
</button>
</span>
</ngx-mat-select-search>
</mat-option>
<mat-option *ngFor="let bank of filteredBanks | async" [value]="bank">
{{bank.name}}
</mat-option>
</mat-select>
</mat-form-field>
</p>
<p>
Selected Bank: {{bankCtrl.value?.name}}
</p>
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
import { Bank, BANKS } from '../demo-data';
import { FormControl } from '@angular/forms';
import { ReplaySubject, Subject } from 'rxjs';
import { MatSelect } from '@angular/material/select';
import { take, takeUntil } from 'rxjs/operators';

@Component({
selector: 'app-custom-no-entries-found-example',
templateUrl: './custom-no-entries-found-example.component.html',
styleUrls: ['./custom-no-entries-found-example.component.scss']
})
export class CustomNoEntriesFoundExampleComponent implements OnInit, AfterViewInit, OnDestroy {

/** list of banks */
protected banks: Bank[] = BANKS;

/** control for the selected bank */
public bankCtrl: FormControl = new FormControl();

/** control for the MatSelect filter keyword */
public bankFilterCtrl: FormControl = new FormControl();

/** list of banks filtered by search keyword */
public filteredBanks: ReplaySubject<Bank[]> = new ReplaySubject<Bank[]>(1);

@ViewChild('singleSelect', { static: true }) singleSelect: MatSelect;

/** Subject that emits when the component has been destroyed. */
protected _onDestroy = new Subject<void>();


constructor() { }

ngOnInit() {
// set initial selection
this.bankCtrl.setValue(this.banks[10]);

// load the initial bank list
this.filteredBanks.next(this.banks.slice());

// listen for search field value changes
this.bankFilterCtrl.valueChanges
.pipe(takeUntil(this._onDestroy))
.subscribe(() => {
this.filterBanks();
});
}

ngAfterViewInit() {
this.setInitialValue();
}

ngOnDestroy() {
this._onDestroy.next();
this._onDestroy.complete();
}

/**
* Sets the initial value after the filteredBanks are loaded initially
*/
protected setInitialValue() {
this.filteredBanks
.pipe(take(1), takeUntil(this._onDestroy))
.subscribe(() => {
// setting the compareWith property to a comparison function
// triggers initializing the selection according to the initial value of
// the form control (i.e. _initializeSelection())
// this needs to be done after the filteredBanks are loaded initially
// and after the mat-option elements are available
this.singleSelect.compareWith = (a: Bank, b: Bank) => a && b && a.id === b.id;
});
}

protected filterBanks() {
if (!this.banks) {
return;
}
// get the search keyword
let search = this.bankFilterCtrl.value;
if (!search) {
this.filteredBanks.next(this.banks.slice());
return;
} else {
search = search.toLowerCase();
}
// filter the banks
this.filteredBanks.next(
this.banks.filter(bank => bank.name.toLowerCase().indexOf(search) > -1)
);
}
}
15 changes: 15 additions & 0 deletions src/app/mat-select-search/mat-select-no-entries-found.directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Directive } from '@angular/core';

/**
* Directive for providing a custom no entries found element.
* e.g.
* <ngx-mat-select-search [formControl]="bankFilterCtrl">
* <span ngxMatSelectNoEntriesFound>
* No entries found <button>Add</button>
* </span>
* </ngx-mat-select-search>
*/
@Directive({
selector: '[ngxMatSelectNoEntriesFound]'
})
export class MatSelectNoEntriesFoundDirective {}
4 changes: 3 additions & 1 deletion src/app/mat-select-search/mat-select-search.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,9 @@

<div *ngIf="_showNoEntriesFound$ | async"
class="mat-select-search-no-entries-found">
{{noEntriesFoundLabel}}
<ng-content *ngIf="noEntriesFound; else defaultNoEntriesFound"
select="[ngxMatSelectNoEntriesFound]"></ng-content>
<ng-template #defaultNoEntriesFound>{{noEntriesFoundLabel}}</ng-template>
</div>
<!--
Copyright (c) 2018 Bithost GmbH All Rights Reserved.
Expand Down
4 changes: 4 additions & 0 deletions src/app/mat-select-search/mat-select-search.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ import { BehaviorSubject, combineLatest, Observable, of, Subject } from 'rxjs';
import { delay, filter, map, startWith, switchMap, take, takeUntil, tap } from 'rxjs/operators';
import { MatSelectSearchClearDirective } from './mat-select-search-clear.directive';
import { configurableDefaultOptions, MAT_SELECTSEARCH_DEFAULT_OPTIONS, MatSelectSearchOptions } from './default-options';
import { MatSelectNoEntriesFoundDirective } from './mat-select-no-entries-found.directive';


/** The max height of the select's overlay panel. */
Expand Down Expand Up @@ -219,6 +220,9 @@ export class MatSelectSearchComponent implements OnInit, OnDestroy, ControlValue
/** Reference to custom search input clear icon */
@ContentChild(MatSelectSearchClearDirective) clearIcon: MatSelectSearchClearDirective;

/** Reference to custom no entries found element */
@ContentChild(MatSelectNoEntriesFoundDirective) noEntriesFound: MatSelectNoEntriesFoundDirective;

@HostBinding('class.mat-select-search-inside-mat-option')
get isInsideMatOption(): boolean {
return !!this.matOption;
Expand Down
8 changes: 6 additions & 2 deletions src/app/mat-select-search/ngx-mat-select-search.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,11 @@ import { CommonModule } from '@angular/common';

import { MatSelectSearchClearDirective } from './mat-select-search-clear.directive';
import { ReactiveFormsModule } from '@angular/forms';
import { MatSelectNoEntriesFoundDirective } from './mat-select-no-entries-found.directive';

export const MatSelectSearchVersion = '4.1.2';
export { MatSelectSearchClearDirective };
export { MatSelectNoEntriesFoundDirective };

@NgModule({
imports: [
Expand All @@ -32,11 +34,13 @@ export { MatSelectSearchClearDirective };
],
declarations: [
MatSelectSearchComponent,
MatSelectSearchClearDirective
MatSelectSearchClearDirective,
MatSelectNoEntriesFoundDirective
],
exports: [
MatSelectSearchComponent,
MatSelectSearchClearDirective
MatSelectSearchClearDirective,
MatSelectNoEntriesFoundDirective
]
})
export class NgxMatSelectSearchModule {
Expand Down

0 comments on commit 7a804d4

Please sign in to comment.