Skip to content

Commit

Permalink
forceAutocompleteSelection attribute directive
Browse files Browse the repository at this point in the history
  • Loading branch information
maryo committed Apr 6, 2020
1 parent f17e204 commit 8d37234
Show file tree
Hide file tree
Showing 3 changed files with 99 additions and 3 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@vanio-cz/angular-material",
"homepage": "https://github.com/vaniocz/angular-material#readme",
"version": "0.0.9",
"version": "0.0.14",
"scripts": {
"ng": "ng",
"start": "ng serve",
Expand Down
7 changes: 5 additions & 2 deletions src/angular-material-module.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import {CommonModule} from '@angular/common';
import {NgModule} from '@angular/core';
import {MatAutocompleteModule} from '@angular/material/autocomplete';
import {MatButtonModule} from '@angular/material/button';
import {MatDialogModule} from '@angular/material/dialog';
import {MatIconModule} from '@angular/material/icon';

import {ConfirmComponent} from './confirm/confirm-component';
import {ForceAutocompleteSelectionDirective} from './force-autocomplete-selection-directive';
import {ToasterComponent} from './toaster/toaster-component';

@NgModule({
imports: [CommonModule, MatButtonModule, MatDialogModule, MatIconModule],
declarations: [ConfirmComponent, ToasterComponent],
imports: [CommonModule, MatAutocompleteModule, MatButtonModule, MatDialogModule, MatIconModule],
exports: [ForceAutocompleteSelectionDirective],
declarations: [ConfirmComponent, ToasterComponent, ForceAutocompleteSelectionDirective],
})
export class AngularMaterialModule {}
93 changes: 93 additions & 0 deletions src/force-autocomplete-selection-directive.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import {Subject} from 'rxjs';
import {take, skipWhile, takeUntil} from 'rxjs/operators';
import {
AfterContentInit,
Directive,
ElementRef,
HostListener,
Input,
OnDestroy,
OnInit,
QueryList,
} from '@angular/core';
import {NgControl} from '@angular/forms';
import {MatAutocomplete, MatAutocompleteSelectedEvent} from '@angular/material/autocomplete';
import {MatOption} from '@angular/material/core';

@Directive({selector: '[forceAutocompleteSelection]'})
export class ForceAutocompleteSelectionDirective implements OnInit, AfterContentInit, OnDestroy {
@Input('matAutocomplete')
private autocomplete?: MatAutocomplete;
private selectedValue?: string;
private unsubscription$: Subject<void> = new Subject();

constructor(private element: ElementRef, private control: NgControl) {}

public ngOnInit(): void {
this.autocomplete?.optionSelected
.pipe(takeUntil(this.unsubscription$))
.subscribe((event: MatAutocompleteSelectedEvent) => {
this.selectedValue = event.option.viewValue;
});
}

public ngAfterContentInit(): void {
this.forceSelection();
}

public ngOnDestroy(): void {
this.unsubscription$.next();
this.unsubscription$.complete();
}

@HostListener('blur', ['$event'])
private onBlur(event: FocusEvent): void {
if (!this.autocomplete) {
return;
}

if (!this.control.control!.value) {
this.selectedValue = '';

return;
}

const autocompleteElement = this.autocomplete.panel?.nativeElement as HTMLElement | undefined;
const relatedTarget = event.relatedTarget as HTMLElement | null;

if (relatedTarget?.tagName.toLowerCase() === 'mat-option' && autocompleteElement?.contains(relatedTarget)) {
return;
}

this.forceSelection();
}

private forceSelection(): void {
if (!this.autocomplete) {
return;
}

if (this.isOptionsLoading(this.autocomplete.options)) {
this.autocomplete.options.changes
.pipe(skipWhile(this.isOptionsLoading.bind(this)), takeUntil(this.unsubscription$), take(1))
.subscribe(this.forceSelection.bind(this));

return;
}

const value = this.element.nativeElement.value.trim().toLowerCase();
const forcedValue = this.autocomplete!.options.find((option: MatOption) => {
return option.viewValue.toLowerCase() === value;
});
this.selectedValue = forcedValue?.viewValue ?? this.selectedValue ?? '';
this.control.control!.setValue(this.selectedValue);
}

private isOptionsLoading(options: QueryList<MatOption>): boolean {
return (
options.length === 1 &&
options.first.disabled &&
options.first._getHostElement().classList.contains('is-loading')
);
}
}

0 comments on commit 8d37234

Please sign in to comment.