Coder Social home page Coder Social logo

bithost-gmbh / ngx-mat-select-search Goto Github PK

View Code? Open in Web Editor NEW
640.0 13.0 128.0 2.51 MB

Angular component providing an input field for searching / filtering MatSelect options of the Angular Material library.

License: MIT License

TypeScript 85.44% JavaScript 0.98% HTML 10.33% SCSS 3.25%
angular material select search filter material2 hacktoberfest

ngx-mat-select-search's Introduction

NgxMatSelectSearch

https://github.com/bithost-gmbh/ngx-mat-select-search

npm version npm downloads total npm downloads monthly CircleCI Donate

What does it do?

Angular component providing an input field for searching / filtering MatSelect options of the Angular Material library.

Example

Try it

See it in action at

Important Note: This project is meant as a temporary implementation of angular/components#5697. The goal is to have an implementation in the official Angular Material repository, a new PR will be created.

Contributions

Contributions are welcome, please open an issue and preferably file a pull request.

Support Development

We aim at providing the best service possible by constantly improving NgxMatSelectSearch and responding fast to bug reports. We do this fully free of cost. If you feel like this library was useful to you and saved you and your business some precious time, please consider making a donation to support its maintenance and further development.

PayPal

Financial Supporters

Thank you very much to our financial supporters!

pschulzk
Philip Viktor Schulz-Klingauf

Collaborators

Contributors

Thank you very much to all our community contributors!

macjohnny
Esteban Gehring
maechler
Markus Mächler
angelaki
Tristan
tonyholt
Tony H
mstawick
Michał Stawicki
alexandrupaul7
Null
blazewalker59
Blaze Walker
achilehero
Cristian Raducanu
damianmigo
Damian Miranda
Danevandy99
Dane Vanderbilt
escheiermann
Edgar Scheiermann
arucar
Erendis
gustavovitor
Gustavo Miranda
meta72
Henno Lauinger
himanshu-singh1995
Null
jfcere
Jean-Francois Cere
josephdecock
Joe DeCock
JomalJohny
Jomal Johny
bulldog98
Jonathan Kolberg
melroy89
Melroy Van Den Berg
AhsanAyaz
Muhammad Ahsan Ayaz
OvidijusStukas
Ovidijus Stukas
qstiegler
Quirin Stiegler
raysuelzer
Ray Suelzer
probert94
Robert Pattis
broekema41
Roland Broekema
swierzbicki
Sebastian Wierzbicki
shenay-aydan
Null
framasev
Stas Amasev
nischi
Thierry Nischelwitzer
vlio20
Vlad Ioffe
WX9yMOXWId
Null
zpaynter
Null
evoltafreak
Joshua
lorenzbaier
Null
ruekart
Null

How to use it?

Install ngx-mat-select-search in your project:

npm install ngx-mat-select-search

Import the NgxMatSelectSearchModule e.g. in your app.module.ts:

import { MatSelectModule } from '@angular/material';
import { NgxMatSelectSearchModule } from 'ngx-mat-select-search';

@NgModule({
  imports: [
    ...
    MatSelectModule,
    NgxMatSelectSearchModule
  ],
})
export class AppModule {}

Use the ngx-mat-select-search component inside a mat-select element by placing it inside a <mat-option> element:

<mat-form-field>
  <mat-select [formControl]="bankCtrl" placeholder="Bank" #singleSelect>
    <mat-option>
      <ngx-mat-select-search [formControl]="bankFilterCtrl"></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>

See the examples in https://github.com/bithost-gmbh/ngx-mat-select-search/tree/master/src/app/examples how to wire the ngx-mat-select-search and filter the options available. Or have a look at https://github.com/bithost-gmbh/ngx-mat-select-search-example to see it in a standalone app.

Template driven forms

You can alternatively use it with template driven forms as follows:

<ngx-mat-select-search ngModel (ngModelChange)="filterMyOptions($event)">

Labels

In order to change the labels, use the inputs specified in the API section as follows:

<ngx-mat-select-search [formControl]="bankFilterCtrl" 
                       placeholderLabel="Find bank..." 
                       noEntriesFoundLabel="'no matching bank found'"></ngx-mat-select-search>

To use the i18n API for translation of the labels, add the corresponding i18n-... attributes:

<ngx-mat-select-search [formControl]="bankFilterCtrl" 
                       placeholderLabel="Find bank..." 
                       i18n-placeholderLabel
                       noEntriesFoundLabel="'no matching bank found'"
                       i18n-noEntriesFoundLabel></ngx-mat-select-search>

Compatibility

Current release

  • @angular/core: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0
  • @angular/material: ^15.0.0 || ^16.0.0 || ^17.0.0 || ^18.0.0 with MatSelectModule (@angular/material/select)

Version 6.0.0

  • @angular/core: ^15.0.0
  • @angular/material: ^15.0.0 with MatLegacySelectModule (@angular/material/legacy-select)

Version 5.0.0

  • @angular/core: ^12.0.0 || ^13.0.0 || ^14.0.0
  • @angular/material: ^12.0.0 || ^13.0.0 || ^14.0.0

Version 3.3.3

  • @angular/core: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0
  • @angular/material: ^8.0.0 || ^9.0.0 || ^10.0.0 || ^11.0.0 || ^12.0.0 || ^13.0.0

Version 1.8.0

  • @angular/core: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0
  • @angular/material: ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0

API

The MatSelectSearchComponent implements the ControlValueAccessor interface. Furthermore, it provides the following inputs:

Inputs

  /** Label of the search placeholder */
  @Input() placeholderLabel = 'Suche';

  /** Type of the search input field */
  @Input() type = 'text';

  /** Font-based icon used for displaying Close-Icon */
  @Input() closeIcon = 'close';

  /** Svg-based icon used for displaying Close-Icon. If set, closeIcon is overridden */
  @Input() closeSvgIcon?: string;

  /** Label to be shown when no entries are found. Set to null if no message should be shown. */
  @Input() noEntriesFoundLabel = 'Keine Optionen gefunden';

  /**
    * Whether or not the search field should be cleared after the dropdown menu is closed.
    * Useful for server-side filtering. See [#3](https://github.com/bithost-gmbh/ngx-mat-select-search/issues/3)
    */
  @Input() clearSearchInput = true;

  /** Whether to show the search-in-progress indicator */
  @Input() searching = false;

  /** Disables initial focusing of the input field */
  @Input() disableInitialFocus = false;

  /** Enable clear input on escape pressed */
  @Input() enableClearOnEscapePressed = false;

  /**
   * Prevents home / end key being propagated to mat-select,
   * allowing to move the cursor within the search input instead of navigating the options
   */
  @Input() preventHomeEndKeyPropagation = false;

  /** Disables scrolling to active options when option list changes. Useful for server-side search */
  @Input() disableScrollToActiveOnOptionsChanged = false;

  /** Adds 508 screen reader support for search box */
  @Input() ariaLabel = 'dropdown search';

  /** Whether to show Select All Checkbox (for mat-select[multi=true]) */
  @Input() showToggleAllCheckbox = false;

  /** select all checkbox checked state */
  @Input() toggleAllCheckboxChecked = false;

  /** select all checkbox indeterminate state */
  @Input() toggleAllCheckboxIndeterminate = false;

  /** Display a message in a tooltip on the toggle-all checkbox */
  @Input() toggleAllCheckboxTooltipMessage = '';

  /** Define the position of the tooltip on the toggle-all checkbox. */
  @Input() toggleAllCheckboxTooltipPosition: 'left' | 'right' | 'above' | 'below' | 'before' | 'after' = 'below';

  /** Show/Hide the search clear button of the search input */
  @Input() hideClearSearchButton = false;

  /** 
   * Always restore selected options on selectionChange for mode multi (e.g. for lazy loading/infinity scrolling). 
   * Defaults to false, so selected options are only restored while filtering is active. 
   */
  @Input() alwaysRestoreSelectedOptionsMulti = false;
  
  /** Output emitter to send to parent component with the toggle all boolean */
  @Output() toggleAll = new EventEmitter<boolean>();

Customize clear icon

In order to customize the search icon, add the ngxMatSelectSearchClear to your custom clear item (a mat-icon or any other element) and place it inside the ngx-mat-select-search component:

<ngx-mat-select-search>
   <mat-icon ngxMatSelectSearchClear>delete</mat-icon>
</ngx-mat-select-search>

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:

<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:

<ngx-mat-select-search>
   <div class="mat-select-search-custom-header-content">something special</div>
</ngx-mat-select-search>

Global default options

Providing the MAT_SELECTSEARCH_DEFAULT_OPTIONS InjectionToken, the default values of several @Input() properties can be set globally. See the documentation of the corresponding @Input() properties of MatSelectSearchComponent.

Example:

import { MAT_SELECTSEARCH_DEFAULT_OPTIONS, MatSelectSearchOptions } from 'ngx-mat-select-search';

@NgModule({
  ...
  providers: [
    {
      provide: MAT_SELECTSEARCH_DEFAULT_OPTIONS,
      useValue: <MatSelectSearchOptions>{
        closeIcon: 'delete',
        noEntriesFoundLabel: 'No options found',
      }
    }
  ]
})
class AppModule {}

Known Problems

  • The currently selected option might be hidden under the search input field when opening the options panel and the panel is at the screen border.

Development

This project was generated with Angular CLI version 1.7.1.

Development server

Run ng serve for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Build

Run ng build to build the project. The build artifacts will be stored in the dist/ directory. Use the -prod flag for a production build.

Library Build / NPM Package

Run npm run build-lib to build the library and generate an NPM package. The build artifacts will be stored in the dist-lib/ folder.

Running unit tests

Run npm run test to execute the unit tests via Karma.

ngx-mat-select-search's People

Contributors

achilehero avatar ahsanayaz avatar alexandrupaul7 avatar angelaki avatar arucar avatar blazewalker59 avatar broekema41 avatar bulldog98 avatar damianmigo avatar danevandy99 avatar dependabot[bot] avatar escheiermann avatar framasev avatar github-actions[bot] avatar gustavovitor avatar himanshu-singh1995 avatar jfcere avatar jomaljohny avatar josephdecock avatar macjohnny avatar maechler avatar melroy89 avatar meta72 avatar mstawick avatar ovidijusstukas avatar probert94 avatar raysuelzer avatar shenay-aydan avatar swierzbicki avatar tonyholt avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

ngx-mat-select-search's Issues

Dynamically change selected values

Hi,

How can I change selected items dynamically?

For example I tried like
this.personMultiCtrl.patchValue([that.persons[5]]);

but selected values don't change.

Rendering issue when selector is at bottom of viewport

Great component but we are just having strange rendering issue when the selector is at the bottom of the viewport. Is there any CSS we can use to override this?
I've tried using disableOptionCentering on the mat-select but this has no effect for me.
Many thanks

image

Add button to noEntriesFoundLabel

Dear @macjohnny !

First of all great work and very usefull extension.

I don't know if and how it is possible but for one of my business cases I have the request to create an new option if it is not found.
Therefor I thought it would be the best way to just show a create-button after the message but currently the content of noEntriesFoundLabel is only text

I have also create a stackblitz for better understanding:
https://stackblitz.com/edit/github-fej2be?file=src%2Fapp%2Fapp.component.ts

Best regards Daniel

Enhancement: Change Input search placeholder value

I noticed this is mentioned in the README file:

** Label of the search placeholder */
@input() placeholderLabel = 'Suche';

But I don't understand how to change its value... Is there a way to provide a different value? If so, could you share an example? If not, could this be built in?

Thanks

AOT support

Hello, when I try to use 1.2.0 in conjunction with ng-packagr and AOT, I get this error:

Cannot read property 'type' of null
TypeError: Cannot read property 'type' of null

It's pointing to code within the angular compiler. Once I comment out the use of the ngx-mat-select-search everything works fine. It's a little tricky to reproduce. It looks like a method calld _createNgFactoryStub in the AOT compiler is the origin of the issue. If you can get to this compiler.umd.js, there's a function in there called removeSummaryDuplicates. This is where its blowing up. I have corrupted my code a bit so it's tough to get you exact lines right now, but if you cannot reproduce i'll get you something more thorough.

Example with Dynamically created FormGroup, specially when the select is inside FormArray

Provide a example using Form Array.

I tried the following with no success:

<ng-container *ngFor="let rule of rules; let i = index">
  <td>
    <ng-container formArrayName="rules">
      <mat-form-field>
        <mat-select [formControlName]="i" [compareWith]="compare">
          <ngx-mat-select-search [formControlName]="'SelectSearch'+i"></ngx-mat-select-search>
          <mat-option *ngFor="let option of rule.options" [value]="option">{{option.name}}</mat-option>
        </mat-select>
      </mat-form-field>
    </ng-container>
  </td>
</ng-container>

The error says:
Cannot find control with path: 'rules -> SelectSearch0'

Bug in IE11: Styling broken and select not useable in automation scenarios

Hi,

in Internet Explorer 11 the styling of the component is broken. It breaks out of the Overlay and there is no Placeholder inside the input.
grafik

Furthermore the component is not useable when automating the application with tools like protractor.
Protractor tries to "scrollIntoView" the specific Option it wants to click onto, but in this case it scrolls right under the search-input, so that it is not clickable.

Selection clears if selected item is not visible in the list by default

I have a select search where I've got thousands of entries to choose from. To make the select render not die trying to show all of that, I changed my filter function to just return the top 10 results. This seems to break the select search. When you pick something outside of the default top 10 results it clears your selection shortly after closing the search list.

Here's an exaggerated example where I took your demo and cut the search results down to top 2: https://stackblitz.com/edit/github-ddlaqr
If you pick Bank A or B it works, but if you search for C and pick bank C, it clears the selection after you make it.

Is there maybe a better way of doing this? Or is there maybe a fix you can see that could be made to your library?

Thanks!

Enchanment: support svg icon for delete button

Hello,

I'm currently using material 6 and using SVG icons. I would like a way to set svgIcon for delete button since now it's hardcoded to "close" google icon font.

Currently I workaround by globally changing '.mat-select-search-clear' to display none. Better without button than text of 'clear'

No results message don't show and disappear on the right time.

Hey,

In the first place well done, great job with this.

I'm using and testing your component and you have a problem with the no results message, so if you write a word you don't have in the list the result only appears if you change focus from the box, and if you escape one char to have results the message only disappear when you put focus out.

You can see that in the images bellow.

Thanks

whatsapp jpg
whatsapp image
whatsapp i jpg

CI

run CI (build and tests)

Filter selection jumps to next entry on reset

Hi,

After typing the first letter into the search field the options are filtered and the first option is then correctly autoselected, so that it can (almost) be set by hitting the Enter key (for example).

However, after a few milliseconds, the next entry in the list is selected automatically. This happens continuously once the search input has been emptied and you start typing again.

The easiest way to reproduce this is through the official stackblitz link, where the problem also occurs:
https://stackblitz.com/github/bithost-gmbh/ngx-mat-select-search-example

Reproduction steps
To reproduce, start på selecting the first bank in the list under single selection (without any search filtering).
Next, type the letter 'b'.
Now it will select 'Bank B' even though 'Bank A' is the first to fulfill the criteria.
Now, use backspace to delete the entered search and type in the letter 'b' once again.
Now the selection will jump to 'Bank C' and so forth.

A gif of the behaviour can be seen here: https://gyazo.com/1f813102b2335ec537e20178b27f337b

The expected use case I have for using this in my app would is to quickly search for an entry and then hit Enter. With this behaviour it will always select the second option if more than one is present, given my search criteria.

Looking at the source code I cannot see anything that would directly cause this problem, which makes me think this might be an Angular Material issue? Can someone help shed some light on this?
I am just surprised there is no existing issues reported for this on this plugin.

It should also be noted that I have tried running this with the newest Angular and Material as well (7.0.1 at the time of writing), which has the same problem. The 'ngx-mat-select-search' version used for that setup is also the newest (1.4.1).

Tree shaking rxjs

This line uses a re-export of the take & takeUntil operators. Ultimately, it's also including the entirety of the rxjs/operators path, without a way for Webpack to tree-shake unused dependencies.

It can be changed to:

import { take } from 'rxjs/operators/take';
import { takeUntil } from 'rxjs/operators/takeUntil';

This reduces my bundle by ~271KB, as seen in the images below.

Before
beforetreeshake

After
aftertreeshake

Compatibility with Angular 7

Package "ngx-mat-select-search" has an incompatible peer dependency to "@angular/cdk" (requires "^5.0.0 || ^6.0.0", would install "7.0.1").

Setting initial value of single select bank group

Hi,
From your example, as shown below, I am trying to set the initial value but have been unsuccessful:

import {
  AfterViewInit, Component, OnDestroy, OnInit, ViewChild
} from '@angular/core'
import { FormBuilder, FormControl, FormGroup } from '@angular/forms'
import { MatSelect } from '@angular/material'
import { Store } from '@ngxs/store'

import { ReplaySubject, Subject } from 'rxjs'
import { take, takeUntil } from 'rxjs/operators'

interface Bank {
  id: string;
  name: string;
}

interface BankGroup {
  name: string;
  banks: Bank[];
}

@Component(
    {
      selector: 'nhncd-mat-select-group-filter-one',
      template: `
        <form
            [formGroup] = 'form'
            autocomplete = 'off'
            fxLayout = 'col wrap'
            fxLayoutAlign = 'start start'
            fxLayoutGap = '15px'>

          <p>
            <mat-form-field>
              <mat-select
                  [formControl] = "bankGroupsCtrl"
                  placeholder = "Bank"
                  #singleSelect>
                <ngx-mat-select-search [formControl] = "bankGroupsFilterCtrl"></ngx-mat-select-search>
                <mat-optgroup
                    *ngFor = "let group of filteredBankGroups | async"
                    [label] = "group.name">
                  <mat-option
                      *ngFor = "let bank of group.banks"
                      [value] = "bank">
                    {{bank.name}}
                  </mat-option>
                </mat-optgroup>
              </mat-select>
            </mat-form-field>
          </p>
          <p>
            Selected Bank: {{bankGroupsCtrl.value?.name}}
          </p>
        </form>
      `
    } )
export class MatSelectGroupFilterOneComponent
    implements OnInit, OnDestroy, AfterViewInit {
  @ViewChild( 'singleSelect' ) singleSelect: MatSelect
  
  /** control for the selected bank for option groups */
  public bankGroupsCtrl: FormControl = new FormControl()
  
  /** control for the MatSelect filter keyword for option groups */
  public bankGroupsFilterCtrl: FormControl = new FormControl()
  
  /** list of bank groups filtered by search keyword for option groups */
  public filteredBankGroups: ReplaySubject<BankGroup[]> = new ReplaySubject<BankGroup[]>( 1 )
  
  /** list of bank groups */
  private bankGroups: BankGroup[] = [
    {
      name: 'Switzerland',
      banks: [
        { name: 'Bank A', id: 'A' },
        { name: 'Bank B', id: 'B' }
      ]
    },
    {
      name: 'France',
      banks: [
        { name: 'Bank C', id: 'C' },
        { name: 'Bank D', id: 'D' },
        { name: 'Bank E', id: 'E' }
      ]
    },
    {
      name: 'Italy',
      banks: [
        { name: 'Bank F', id: 'F' },
        { name: 'Bank G', id: 'G' },
        { name: 'Bank H', id: 'H' },
        { name: 'Bank I', id: 'I' },
        { name: 'Bank J', id: 'J' }
      ]
    },
    {
      name: 'United States of America',
      banks: [
        { name: 'Bank Kolombia', id: 'K' }
      ]
    },
    {
      name: 'Germany',
      banks: [
        { name: 'Bank L', id: 'L' },
        { name: 'Bank M', id: 'M' },
        { name: 'Bank N', id: 'N' },
        { name: 'Bank O', id: 'O' },
        { name: 'Bank P', id: 'P' },
        { name: 'Bank Q', id: 'Q' },
        { name: 'Bank R', id: 'R' }
      ]
    }
  ]
  
  form: FormGroup
  
  @ViewChild( 'singleSelect' ) singleSelect: MatSelect
  @ViewChild( 'multiSelect' ) multiSelect: MatSelect
  
  /** Subject that emits when the component has been destroyed. */
  private _onDestroy = new Subject<void>()
  
  constructor(
      private _fb: FormBuilder,
      private _store$: Store ) {
  }
  
  createForm() {
    this.form = this._fb.group( {} )
  }
  
  ngOnInit() {
    this.createForm()
    
    // DOES NOT WORK
    this.bankGroupsCtrl.setValue( 'Bank C' )
    
    // load the initial bank list
    this.filteredBankGroups.next( this.copyBankGroups( this.bankGroups ) )
    
    // listen for search field value changes
    this.bankGroupsFilterCtrl.valueChanges
        .pipe( takeUntil( this._onDestroy ) )
        .subscribe( () => {
          this.filterBankGroups()
        } )
  }
  
  ngAfterViewInit() {
    this.setInitialValue()
  }
  
  ngOnDestroy() {
    this._onDestroy.next()
    this._onDestroy.complete()
  }
  
  /**
   * Sets the initial value after the filteredBanks are loaded initially
   */
  private setInitialValue() {

//    this.bankGroupsCtrl.setValue('Bank C')
    this.filteredBankGroups
        .pipe( take( 1 ), takeUntil( this._onDestroy ) )
        .subscribe( () => {
          this.singleSelect.compareWith =
              ( a: Bank, b: Bank ) => a && b && a.id === b.id
        } )


  }
  
  private filterBankGroups() {
    if ( !this.bankGroups ) {
      return
    }
    // get the search keyword
    let search = this.bankGroupsFilterCtrl.value
    const bankGroupsCopy = this.copyBankGroups( this.bankGroups )
    if ( !search ) {
      this.filteredBankGroups.next( bankGroupsCopy )
      return
    }
    else {
      search = search.toLowerCase()
    }
    // filter the banks
    this.filteredBankGroups.next(
        bankGroupsCopy.filter( bankGroup => {
          const showBankGroup = bankGroup.name.toLowerCase()
                                         .indexOf( search ) > -1
          if ( !showBankGroup ) {
            bankGroup.banks =
                bankGroup.banks.filter( bank => bank.name.toLowerCase()
                                                    .indexOf( search ) > -1 )
          }
          return bankGroup.banks.length > 0
        } )
    )
  }
  
  private copyBankGroups( bankGroups: BankGroup[] ) {
    const bankGroupsCopy = []
    bankGroups.forEach( bankGroup => {
      bankGroupsCopy.push( {
                             name: bankGroup.name,
                             banks: bankGroup.banks.slice()
                           } )
    } )
    return bankGroupsCopy
  }
}

Could you let me know what I am doing incorrectly?

Pressing tab on control moves focus to body

I tested it for multi-select. Whenever the dropdown is open, the focus is always on search input field. Whenever we press tab on that the focus moves on to 'body' rather than next element. this is causing accessibility issue. Could you please look into this.

Instead of static bank values, passed the dynamic data but search is not working

Hello,

I used the ngx-mat-select-search example in my angular project. This works fine. But instead of static bank values if I pass dynamic data as option then search is not working. How to resolve this? My code is as follows:

HTML

<mat-form-field>
  <mat-select [formControl]="bankCtrl"  #singleSelect>
    <ngx-mat-select-search [formControl]="bankFilterCtrl"></ngx-mat-select-search>
    <mat-option value = "select">Select bank</mat-option>
    <mat-option *ngFor="let app of apps" [value]="app">
      {{app.name}}
    </mat-option>
  </mat-select>
</mat-form-field>

TS

import { Component, NgZone, OnInit, OnDestroy, ViewChild, HostListener, ViewEncapsulation } from '@angular/core';
import { Router, NavigationEnd } from '@angular/router';
//import { MenuItems } from '../../shared/menu-items/menu-items';
import { MenuItems } from '../../bridle-menus/menu-items/menu-items';
import { Subscription } from 'rxjs/Subscription';
import 'rxjs/add/operator/filter';

import { TranslateService } from '@ngx-translate/core';

import { PerfectScrollbarConfigInterface, PerfectScrollbarDirective } from 'ngx-perfect-scrollbar';
import { ClientService, User, LocationService, ProcessAdminServices, Userinfo } from '../../shared/services/index';
import { AuthService } from "../../shared/services/auth.service";
import { env_host } from "../../shared/services/endpoints";
import { Globals } from '../../shared/services/globals';

import { AppDefinitionRepresentationModel, ProcessService, ProcessDefinitionRepresentation, ProcessInstance } from "../../process-services/index";
import {FormControl} from '@angular/forms';
import { Observable, ReplaySubject, Subject } from 'rxjs';
import { map, startWith, takeUntil, take } from 'rxjs/operators';
import { AppsProcessService, AuthenticationService, LogService, UserPreferencesService } from "../../core/index";
//added on 22-10-2018
import { VERSION, MatSelect } from '@angular/material';

const SMALL_WIDTH_BREAKPOINT = 960;
//added on 22-10-2018
interface Bank {
/** id: number;
 name: string;**/
 defaultAppId: string;
    deploymentId: string;
    name: string;
    description: string;
    theme: string;
    icon: string;
    id: number;
    modelId: number;
    tenantId: number;
}
@Component({
  selector: 'app-layout',
  templateUrl: './admin-layout.component.html',
  styleUrls: ['admin-layout.component.scss'],
  providers:[ClientService,AuthService,LocationService,ProcessAdminServices,AppsProcessService,ProcessService],
  encapsulation: ViewEncapsulation.None
})
export class AdminLayoutComponent implements OnInit, OnDestroy {
  banks: any;

  private _router: Subscription;

  mediaMatcher: MediaQueryList = matchMedia(`(max-width: ${SMALL_WIDTH_BREAKPOINT}px)`);
  today: number = Date.now();
  url: string;
  showSettings = false;
  dark: boolean;
  boxed: boolean;
  collapseSidebar: boolean;
  compactSidebar: boolean;
  currentLang = 'en';
  dir = 'ltr';
  sidePanelOpened;
  user;
  scopes: string;
  returnscope: string;
  userinform = [];
  locationId: string;
  dspName: string;
  dropdownStyle: string;
  apps = [];
   //Bank=[];
  statusCode: number;
  currentApp: string = "";
  selectedApp= [];
  myapp;
  processDefinitions: ProcessDefinitionRepresentation[] = [];
  processDefinitionKey:string;
  start:string="start";
  authenticated: boolean;
  userinfo: Userinfo;
  @ViewChild('sidemenu') sidemenu;
  @ViewChild(PerfectScrollbarDirective) directiveScroll: PerfectScrollbarDirective;

  public config: PerfectScrollbarConfigInterface = {};
  //Added on 22-10-2018
  version = VERSION;
  /** control for the selected bank */
  public bankCtrl: FormControl = new FormControl();

   /** control for the MatSelect filter keyword */
  public bankFilterCtrl: FormControl = new FormControl();
/*private banks: Bank[] = [
    {name: 'Bank A (Switzerland)', id: 'A'},
    {name: 'Bank B (Switzerland)', id: 'B'},
    {name: 'Bank C (France)', id: 'C'},
    {name: 'Bank D (France)', id: 'D'},
    {name: 'Bank E (France)', id: 'E'},
    {name: 'Bank F (Italy)', id: 'F'},
    {name: 'Bank G (Italy)', id: 'G'},
    {name: 'Bank H (Italy)', id: 'H'},
    {name: 'Bank I (Italy)', id: 'I'},
    {name: 'Bank J (Italy)', id: 'J'},
    {name: 'Bank Kolombia (United States of America)', id: 'K'},
    {name: 'Bank L (Germany)', id: 'L'},
    {name: 'Bank M (Germany)', id: 'M'},
    {name: 'Bank N (Germany)', id: 'N'},
    {name: 'Bank O (Germany)', id: 'O'},
    {name: 'Bank P (Germany)', id: 'P'},
    {name: 'Bank Q (Germany)', id: 'Q'},
    {name: 'Bank R (Germany)', id: 'R'}
  ];*/

    /** list of banks filtered by search keyword */
  public filteredBanks: ReplaySubject<Bank[]> = new ReplaySubject<Bank[]>(1);
    @ViewChild('singleSelect') singleSelect: MatSelect;
    /** Subject that emits when the component has been destroyed. */
  private _onDestroy = new Subject<void>();

  constructor(
    private router: Router, public menuItems: MenuItems, private globals: Globals,
    public translate: TranslateService, zone: NgZone,private clientService:ClientService,private authService: AuthService,private processAdminServices: ProcessAdminServices,
    private appsProcessService:AppsProcessService,
    private processService: ProcessService,
    private authenticationService: AuthenticationService,
        private logService: LogService,
        private userPreferenceService: UserPreferencesService

) {
          userPreferenceService.authType='BPM';

    const browserLang: string = translate.getBrowserLang();
    translate.use(browserLang.match(/en|fr/) ? browserLang : 'en');
    this.mediaMatcher.addListener(mql => zone.run(() => {
      this.mediaMatcher = mql;
    }));
  }

  ngOnInit(): void {
    this.getScopes();
    this.login();
    this.getUserInfo();
    this.url = this.router.url;
    this.dropdownStyle = "dropdown-content";
 //ADDED ON 22-10-2018
    // set initial selection
    this.bankCtrl.setValue(this.apps[0]);
     // load the initial bank list
    this.filteredBanks.next(this.apps.slice());
// listen for search field value changes
    this.bankFilterCtrl.valueChanges
      .pipe(takeUntil(this._onDestroy))
      .subscribe(() => {
        this.filterBanks();
      });


      

    this._router = this.router.events.filter(event => event instanceof NavigationEnd).subscribe((event: NavigationEnd) => {
      document.querySelector('.app-inner > .mat-drawer-content > div').scrollTop = 0;
      this.url = event.url;
      this.runOnRouteChange();
    });

   
  }

//ADDED on 22-10-2018
ngAfterViewInit() {
    this.setInitialValue();
  }

   /**
   * Sets the initial value after the filteredBanks are loaded initially
   */
  private 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;
       // this.multiSelect.compareWith = (a: Bank, b: Bank) => a && b && a.id === b.id;
      });
  }
  ngOnDestroy(): void  {
    this._router.unsubscribe();
    //added on 22-10-2018
     this._onDestroy.next();
    this._onDestroy.complete();
  }

  runOnRouteChange(): void {
    if (this.isOver()) {
      this.sidemenu.close();
    }

    this.updatePS();
  }

  isOver(): boolean {
    if (this.url === '/apps/messages' ||
      this.url === '/apps/calendar' ||
      this.url === '/apps/media' ||
      this.url === '/maps/leaflet' ||
      this.url === '/taskboard') {
      return true;
    } else {
      return this.mediaMatcher.matches;
    }
  }

  menuMouseOver(): void {
    if (this.mediaMatcher.matches && this.collapseSidebar) {
      this.sidemenu.mode = 'over';
    }
  }

  menuMouseOut(): void {
    if (this.mediaMatcher.matches && this.collapseSidebar) {
      this.sidemenu.mode = 'side';
    }
  }

  updatePS(): void  {
    if (!this.mediaMatcher.matches && !this.compactSidebar) {
      setTimeout(() => {
        this.directiveScroll.update();
      }, 350);
    }
  }

  // addMenuItem(): void {
  //   this.menuItems.add({
  //     state: 'menu',
  //     name: 'MENU',
  //     type: 'sub',
  //     icon: 'trending_flat',
  //     role: 'admin',
  //     children: [
  //       {state: 'menu', name: 'MENU'},
  //       {state: 'timeline', name: 'MENU'}
  //     ]
  //   });
  // }

logout(){
    //alert("coming in logout");
    // this.clientService.logout();
    // this.router.navigate(["/"]);
  this.clientService.logout()
     /**.subscribe(
 data => {
              this.router.navigate(['']);      
        });**/
        .subscribe(successCode => {
        //alert("logged out");
				  // this.router.navigate(['/']);
          // this.router.navigate(['']);
          // this.router.navigateByUrl('');
          // this.returnUrl = this.route.snapshot.queryParams['localhost:8088'] || '/';
           //alert("returnUrl Is:"+this.returnUrl);
                 //              this.router.navigate([this.returnUrl]);
                                  //localStorage.removeItem('loggedInUserName');

    window.location.href=env_host+"/";

				 }
			   );
  }   

  getScopes() {
    this.authService.getScopes()
      .subscribe(
      data => {
        this.scopes = data;
        console.log("scopes=>"+this.scopes);
        if (this.scopes.includes("admin")) {
          this.returnscope = "yes";
          return this.returnscope
        }
      });
  }

  getUserInfo(){
    this.clientService.getUserInform()
      .subscribe(
        data => {

          this.userinform = data;
          console.log("userInform data=>",this.userinform);
          //localStorage.setItem('loggedInUserName', this.userinform[0].username);
          this.dspName= this.userinform[0].displayname;
          this.locationId = this.userinform[0].location_id;
          //var loggedInUser = localStorage.getItem('loggedInUserName');
          //this.getApps(loggedInUser);
}
) 
}

public getLocationId(locationIdAndName: any) {
  this.globals.locationIdAndName = locationIdAndName;
}

//lists logged in user's applications 
getApps(loggedInUser) {
                  this.processAdminServices.getApps(loggedInUser)
	       .subscribe(
              data =>{ 
                let appsData = data["data"];
                      this.apps = appsData.filter(app => { 
                      if(app.id != null) { 
                     return app;
                      } 
                      
                     });
                      },
              errorCode =>  {this.statusCode = errorCode}
              
                            );

 }

//On select application from drop down should redirect to start process form of the selected app
    onAppClickToStartProcess(myapp) {
    this.selectedApp = this.getSelectedAppByName(myapp);
     
  let appName = this.selectedApp['name'];
     let appId = this.selectedApp['id'];
                  this.processService.getProcessDefinitions(appId).subscribe(

        data => {
          this.processDefinitions = data;
       
          this.processDefinitionKey = this.processDefinitions[0].key;
         this.router.navigate(['app-list/activiti/start1',appId || 0,appName,this.processDefinitionKey,this.start,this.processDefinitions[0].id]);
 
       });
    }
  //get selected application details by app name  
getSelectedAppByName(myapp: string) {
    return this.apps.find(app => app.name === myapp);
}

public enableDropdown() {
  if (this.dropdownStyle == "dropdown-content") {
    this.dropdownStyle = "dropdown-content-enable";
  }
  else {
    this.dropdownStyle = "dropdown-content";
  }
}

@HostListener('mouseup')
  onMouseUp() {
    if (this.dropdownStyle == "dropdown-content-enable") {
    this.dropdownStyle = "dropdown-content";
    }
  }

  login() {
    this.clientService.getUserInfo()
          .subscribe(
            data => {
    
              this.userinfo = data;
                 this.authenticationService.login(this.userinfo.email, 'Bridle@456', true)
        .subscribe(
            (ticket: any) => {
              
              this.logService.info(ticket);
               this.authenticated=true;
               if(this.authenticated == true){
        this.getDeployedApps();
        }
            },
            (err: any) => {
               // this.actualLoginStep = LoginSteps.Landing;
               this.authenticated=false;
            },
            () => console.log('Login done')
            
        );}
    )
      }

      getDeployedApps() {

        this.appsProcessService.getDeployedApplications()
.subscribe(
    data =>{ 
      let appsData = data;
      
            this.apps = appsData.filter(app => { 
            if(app.id != null) { 
           return app;

            } 
           });
           /**this.Bank = appsData.filter(app => { 
            if(app.id != null) { 
           return app;

            } 
           });**/
          let Bank = appsData.filter(app => { 
            if(app.id != null) { 
           return app;

            } 
           });
           console.log("BANK DETAILS");
          // console.dir(this.Bank);
            },
                                 

    errorCode =>  {this.statusCode = errorCode}
    
                  );

}

//ADDED on 22-10-2018
private filterBanks() {
    if (!this.apps) {
      return;
    }
    // get the search keyword
    let search = this.bankFilterCtrl.value;
    if (!search) {
      this.filteredBanks.next(this.apps.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    // filter the banks
    this.filteredBanks.next(
      this.apps.filter(app => app.name.toLowerCase().indexOf(search) > -1)
    );
  }

}

Please provide solution for solving this issue.

Thanks & Regards,
Shilpa Kulkarni

Error when quickly selection option

Cannot read property 'nativeElement' of undefined
at MatSelectSearchComponent.push../node_modules/ngx-mat-select-search/esm5/ngx-mat-select-search.js.MatSelectSearchComponent._focus (ngx-mat-select-search.js:146)

MatSelectSearchComponent.prototype._focus = function () {
if (!this.searchSelectInput) {
return;
}
var panel = this.matSelect.panel.nativeElement;

This is the code that is failing. There should be probably nullcheck added

Material Design integration

First: Thank you for this awesome addition!

Sadly this Component doesn't seem to be "material-theme"-aware. There are 3 Design issues i can see.

  1. This Component does not use the Material-Typography. But this can easily be fixed by adding the "mat-typography" class to the Element.

  2. The parent Div width of the Input is calculated as "100% + 15px" which is enough if you use the standard scrollbars. But if you redesign them for Chrome Browsers and make them thinner then the Input does not align to the scrollbar.

  3. The Color. At the moment the background and border color seems to be "hard-coded". But they are different if you use the Material Dark Theme by doing "mat-dark-theme($primary, $accent, $warn)".

Maybe there is an easy fix i am not aware of.
Again: thanks for your awesome component.

Angular 6 Support

Hey!
What do you think about adding Angular 6 Support?
I am not sure but i think it would not be possible to support older versions then... Maybe a ng6 branch? The Problem is that Angular 6 is using the RxJS v6 and there imports have changed. Also "takeUntil" now needs to be piped and stuff. I can make a PR for that but i don't know how you feel about the fact that this may lead to a breaking change in a way where it won't support older Angular versions anymore...

Binding data to child component with Observables

In order to use ngx-mat-select-search in a simple use-case i needed to write bunch of lines of code, but i wanted to use it easier and i wrote a component below.

code in app-select-search.component.ts

import { Component, OnInit, AfterViewInit, OnDestroy, ViewChild, Input, EventEmitter, Output, ChangeDetectorRef } from '@angular/core';
import { MatSelect } from '@angular/material';
import { ReplaySubject, Subject, Observable } from 'rxjs';
import { FormControl } from '@angular/forms';
import { takeUntil, take } from 'rxjs/operators';

@Component({
  selector: 'app-select-search',
  template: `
  <mat-form-field>
    <mat-select [formControl]="entityCtrl" #singleSelect (selectionChange)="onChange($event)">
      <ngx-mat-select-search  
        [formControl]="entityFilterCtrl"
        [placeholderLabel]="Search"
        [noEntriesFoundLabel]="No Result Found">
      </ngx-mat-select-search>
      <mat-option *ngFor="let entity of filteredEntities  | async" [value]="entity.id">{{entity.name}}</mat-option>
    </mat-select>
  </mat-form-field>
`
})
export class MechanicSelectComponent implements OnInit, AfterViewInit, OnDestroy {

  /** control for the selected entity */
  public entityCtrl: FormControl = new FormControl();

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

  /** list of entities filtered by search keyword */
  public filteredEntities: ReplaySubject<any[]> = new ReplaySubject<any[]>(1);

  @ViewChild('singleSelect') singleSelect: MatSelect;

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

  @Input() data: any[];

  @Output() onSelectionChange: EventEmitter<any> = new EventEmitter<any>();

  constructor() { }

  ngOnInit(): void {
    // set initial selection
    this.entityCtrl.setValue(this.data);

    // load the initial entity list
    this.filteredEntities.next(this.data.slice());


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

  }

  ngAfterViewInit(): void {
    this.setInitialValue();
  }

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

  onChange($event) {
    this.onSelectionChange.emit($event);
  }

  private setInitialValue() {
    this.filteredEntities
      .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 filteredEntities are loaded initially
        // and after the mat-option elements are available
        this.singleSelect.compareWith = (a: any, b: any) => a.id === b.id;
      });
  }

  private filterEntities() {
    if (!this.data) {
      return;
    }
    // get the search keyword
    let search = this.entityFilterCtrl.value;
    if (!search) {
      this.filteredEntities.next(this.data.slice());
      return;
    } else {
      search = search.toLowerCase();
    }
    // filter the entitys
    this.filteredEntities.next(
      this.data.filter(entity => entity.name.toLowerCase().indexOf(search) > -1)
    );
  }
}

Here is how i use it.
code in test.component.ts

<app-select-search [data]="countries" (onSelectionChange)="onCountriesChange($event)">
</app-select-search>
countries: Country[] = [];

ngOnInit() {

    this.countriesService.getAllCountries().subscribe(result => {
        this.countries = result;
    });
}

The problem is that data/countries is empty on init that's why select's opts are empty. But eventually after subscribing data/countries will be provided. Unfortunately provided data doesn't appear in select. What i'm missing?

mat-select-search selecting from drop down issue

Hello,

I just npm install the ngx-mat-select-search I occurred a bug when I filter the drop-down then select an item, the item is selected, then in a moment the drop-down select the default value
Step to reproduce:

  1. Filter the list all work fine
  2. Select an option the item will be displayed as usual
  3. then in just a moment is reselecting the first item in the source list

this bug occurs only if I filter the list.
I noticed that on dropdown close the clearing of the input text is repopulated the filtered list.

Plus when the clearSearchInput is false all work fine but when its true the problem is apparent,

Can you help me with the problem

Regard and thanx

Bug with special characters.

I just found that the search fields has a bug when using special characters. If you type in an special character, like 'é' it shows the 'noEntriesFoundLabel'. At first it looks like the input got blocked, and for some reason if you try to delete with the "backspace" key (keyCode === 8) at this point, it triggers the history.back() action of the browser.

appreciation

Man, this looks very nice. I have started to use this select-search component in my project and it is working just fine so far. Few issues already identified - should be easy to fix / address. I've wrapped the control in another component which I can reuse. Starred the repo, and whenever there is a new version of this code / new implementation (probably from Angular/Material team) I will just switch the content where needed.

Good job.

Avoid logic over ViewChild selector

mat-select [formControl]="bankCtrl" placeholder="Bank" #singleSelect

ngx-mat-select-search depends on having either the ViewChild named #singleSelect or #multiSelect.
This prohibits us to change the selection style using a variable.
Forcing the ViewChild's name to be #singleSelect or #multiSelect should be avoided.

Rather use multiple="true" as in @input field or something similar.

See also:
https://material.angular.io/components/select/overview#multiple-selection

Set selected value

I'm using ngx-mat-select-search in my application.It's working fine.But while editing the form i can't the set the selected option.

In sample based in array index it has been set as selected.

Below is my JSON loading into options.

[
  {
    "countryId": "1",
    "countryName": "Afghanistan"
  },
  {
    "countryId": "10",
    "countryName": "Antigua and Barbuda"
  },
  {
    "countryId": "100",
    "countryName": "Hong Kong"
  }
]

I need to set the selection option based on countryId.

Below is my sample code.

<mat-form-field class="county-part dropdown-inside-box">
  <mat-select [formControl]="countryCtrl" placeholder="" #singleSelect>
	<ngx-mat-select-search [formControl]="countryFilterCtrl" placeholderLabel="Search by name" noEntriesFoundLabel='No results found'></ngx-mat-select-search>
	<mat-option *ngFor="let sr of filteredCountry | async" [value]="sr.countryId" (click)="getCountryId(sr.countryId)">
	  {{sr.countryName}}
	</mat-option>
  </mat-select>
  <img src="../../assets/images/arrow-black-down.svg" class="black-arrow-img">
  <mat-error *ngIf="isFieldInvalid('countryCtrl')">
	Please enter Country
  </mat-error>
</mat-form-field>

And also how can set the selected options in multiselect?

Thanks,

import problem NgxMatSelectSearchModule

Hi,
I tried to import NgxMatSelectSearchModule into app.module.ts and I get this error.

If 'ngx-mat-select-search' is an Angular component, then verify that it is part of this module.

also I tried to import to only my module and I get same error. Could you help me?

"@angular/animations": "6.0.2",
"@angular/cdk": "6.0.2",
"@angular/common": "6.0.2",
"@angular/compiler": "6.0.2",
"@angular/core": "6.0.2",
"@angular/flex-layout": "6.0.0-beta.15",
"@angular/forms": "6.0.2",
"@angular/http": "6.0.2",
"@angular/material": "6.0.2",

when placed ngIf on ngx-mat-select-search error

When using ngIf to hide search, I've got following error.
version of angular: Angular6-latest
ngx-select: latest

TRACEBACK:

TableFiltersComponent.html:18 ERROR TypeError: Cannot read property 'changes' of undefined
    at MatSelectSearchComponent.push../node_modules/ngx-mat-select-search/esm5/ngx-mat-select-search.js.MatSelectSearchComponent.ngAfterViewInit (ngx-mat-select-search.js:97)
    at callProviderLifecycles (core.js:9567)
    at callElementProvidersLifecycles (core.js:9541)
    at callLifecycleHooksChildrenFirst (core.js:9531)
    at checkAndUpdateView (core.js:10467)
    at callViewAction (core.js:10699)
    at execEmbeddedViewsAction (core.js:10662)
    at checkAndUpdateView (core.js:10459)
    at callViewAction (core.js:10699)
    at execEmbeddedViewsAction (core.js:10662)
View_TableFiltersComponent_7 @ TableFiltersComponent.html:18
push../node_modules/@angular/core/fesm5/core.js.DebugContext_.logError @ core.js:11313
push../node_modules/@angular/core/fesm5/core.js.ErrorHandler.handleError @ core.js:1719
(anonymous) @ core.js:4588
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:388
push../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:138
push../node_modules/@angular/core/fesm5/core.js.NgZone.runOutsideAngular @ core.js:3783
push../node_modules/@angular/core/fesm5/core.js.ApplicationRef.tick @ core.js:4588
(anonymous) @ core.js:4473
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:388
onInvoke @ core.js:3824
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invoke @ zone.js:387
push../node_modules/zone.js/dist/zone.js.Zone.run @ zone.js:138
push../node_modules/@angular/core/fesm5/core.js.NgZone.run @ core.js:3738
next @ core.js:4473
schedulerFn @ core.js:3555
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.__tryOrUnsub @ Subscriber.js:195
push../node_modules/rxjs/_esm5/internal/Subscriber.js.SafeSubscriber.next @ Subscriber.js:133
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber._next @ Subscriber.js:77
push../node_modules/rxjs/_esm5/internal/Subscriber.js.Subscriber.next @ Subscriber.js:54
push../node_modules/rxjs/_esm5/internal/Subject.js.Subject.next @ Subject.js:47
push../node_modules/@angular/core/fesm5/core.js.EventEmitter.emit @ core.js:3539
checkStable @ core.js:3793
onLeave @ core.js:3860
onInvokeTask @ core.js:3818
push../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask @ zone.js:420
push../node_modules/zone.js/dist/zone.js.Zone.runTask @ zone.js:188
push../node_modules/zone.js/dist/zone.js.ZoneTask.invokeTask @ zone.js:496
invokeTask @ zone.js:1540
globalZoneAwareCallback @ zone.js:1566
TableFiltersComponent.html:18 ERROR CONTEXT DebugContext_ {view: {…}, nodeIndex: 2, nodeDef: {…}, elDef: {…}, elView: {…}}

adding icon to mat-select group name

Hi,
This a request, NOT an issue.
Would you please consider adding the ability for the name of a group to support an icon as well as the name.

Consider the following:

<mat-select
                #multiSelect
                [formControl] = "multiOptionCtrl"
                [multiple] = "true"
                [placeholder] = 'placeholder'>
              <ngx-mat-select-search
                  [formControl] = "multiOptionCtrl"></ngx-mat-select-search>
              <mat-optgroup
                  *ngFor = "let group of multiOptionsFilter | async"
                  [label] = "group.name">
                <mat-icon
                    [svgIcon] = 'group?.icon'
                    class = 'mrg-rt-5px'
                    color = 'accent'></mat-icon>
                <mat-option
                    *ngFor = "let option of group.options"
                    [value] = "option.value">
                  <mat-icon
                      [svgIcon] = 'option?.icon'
                      class = 'mrg-rt-5px'
                      color = 'accent'></mat-icon>
                  {{option.viewValue}}
                </mat-option>
              </mat-optgroup>
            </mat-select>

<mat-icon
[svgIcon] = 'group?.icon'
class = 'mrg-rt-5px'
color = 'accent'>

does not display the icon.

I suggest maybe group.icon. Please let me know what you think.

Thanks

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.