Skip to content

Instantly share code, notes, and snippets.

@nettakogo87
Created August 16, 2019 15:02
Show Gist options
  • Save nettakogo87/6e0a62722fdd7e2be278a754af665130 to your computer and use it in GitHub Desktop.
Save nettakogo87/6e0a62722fdd7e2be278a754af665130 to your computer and use it in GitHub Desktop.
Angular control
<mat-form-field class="evv-full-width">
<mat-placeholder>{{placeholder}}</mat-placeholder>
<mat-chip-list
#chipList
[disabled]="itemsCtrl.disabled"
[required]="required">
<mat-chip
*ngFor="let selectedItem of selectedItems; let index = index"
color="primary"
selected="false"
[removable]="itemsCtrl.enabled"
(removed)="removeByIndex(index)">
<ng-container
*ngTemplateOutlet="itemRowTemplateRef; context: {$implicit: selectedItem}">
</ng-container>
<mat-icon matChipRemove>cancel</mat-icon>
</mat-chip>
<input
matInput
#itemsInput
[formControl]="itemsCtrl"
[matAutocomplete]="autoItem"
[matChipInputFor]="chipList"
[attr.aria-label]="placeholder">
</mat-chip-list>
<mat-autocomplete
#autoItem="matAutocomplete"
(optionSelected)="select($event)">
<mat-option
*ngFor="let filteredItem of filteredItems | async"
[value]="filteredItem">
<ng-container
*ngTemplateOutlet="itemRowTemplateRef; context: {$implicit: filteredItem}">
</ng-container>
</mat-option>
</mat-autocomplete>
<mat-error *ngIf="required">
<ng-container *ngTemplateOutlet="itemErrorsTemplateRef; context: {$implicit: multi}">
</ng-container>
</mat-error>
</mat-form-field>
<!-- Templates -->
<ng-content>
</ng-content>
// ANGULAR
import {
ChangeDetectionStrategy,
Component,
ElementRef,
EventEmitter,
Input,
OnDestroy,
OnInit,
Optional,
Output,
Self,
ViewChild,
} from '@angular/core';
import { ControlValueAccessor, FormControl, NgControl } from '@angular/forms';
import { MatAutocompleteSelectedEvent } from '@angular/material';
// RXJS
import { Observable, Subject } from 'rxjs';
import { debounceTime, takeUntil } from 'rxjs/operators';
@Component({
selector: 'evv-chips-autocomplete',
templateUrl: './chips-autocomplete.component.html',
styleUrls: ['./chips-autocomplete.component.scss'],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class ChipsAutocompleteComponent<T> implements OnInit, OnDestroy, ControlValueAccessor {
@Input()
public placeholder = 'Select';
@Input()
public filteredItems: Observable<T[]>;
@Input()
public multi = false;
@Input()
public required = false;
@Input()
public itemRowTemplateRef: any;
@Input()
public itemErrorsTemplateRef: any;
@Output()
public searchChanged = new EventEmitter<string>();
@ViewChild('chipList')
public chipList;
@ViewChild('itemsInput')
public itemsInput: ElementRef;
public itemsCtrl = new FormControl('');
protected _onChange = (value: any) => {};
protected _onTouched = () => {};
private _selectedItems: T[] = [];
private _destroyed$ = new Subject<void>();
constructor(
@Optional() @Self() private _ngControl: NgControl,
) {
if (this._ngControl) {
this._ngControl.valueAccessor = this;
}
}
public get selectedItems(): T[] {
return this._selectedItems;
}
public ngOnInit(): void {
this._subItemsControlChanged();
this._subStatusChanged();
}
public ngOnDestroy(): void {
this._destroyed$.next();
this._destroyed$.complete();
}
public registerOnChange(fn: any): void {
this._onChange = fn;
}
public registerOnTouched(fn: any): void {
this._onTouched = fn;
}
public setDisabledState(isDisabled: boolean): void {
if (isDisabled) {
this.itemsCtrl.disable();
} else {
this.itemsCtrl.enable();
}
}
public writeValue(items: T[]): void {
this._selectedItems = items.slice();
}
public select(event: MatAutocompleteSelectedEvent): void {
if (event.option.value) {
if (!this.multi) {
this._selectedItems.length = 0;
}
this._selectedItems.push(event.option.value);
this._onChange(this._selectedItems.slice());
}
this.itemsInput.nativeElement.value = '';
this.itemsCtrl.setValue('');
}
public removeByIndex(index: number): void {
this._selectedItems.splice(index, 1);
this.itemsCtrl.setValue('');
this._onChange(this._selectedItems.slice());
}
private _subItemsControlChanged(): void {
this.itemsCtrl.valueChanges
.pipe(
debounceTime(500),
takeUntil(this._destroyed$),
)
.subscribe((searchQuery: string) => {
this.searchChanged.emit(searchQuery);
});
}
private _subStatusChanged(): void {
this._ngControl.statusChanges
.pipe(
takeUntil(this._destroyed$),
)
.subscribe((ctrlStatus) => {
this.chipList.errorState = ctrlStatus === 'INVALID';
});
}
}
<evv-chips-autocomplete
#selectRecipientsInput
class="pad-bottom-sm"
placeholder="Select Recipients"
[formControl]="recipientsCtrl"
[filteredItems]="filteredRecipients"
[multi]="false"
[required]="true"
[itemRowTemplateRef]="itemRow"
[itemErrorsTemplateRef]="itemErrorMessages"
(searchChanged)="searchChanged($event)">
<ng-template #itemRow let-recipient>
<evv-select-recipient-template [recipient]="recipient">
</evv-select-recipient-template>
</ng-template>
<ng-template #itemErrorMessages let-multi>
<ng-container *ngIf="multi">Recipients are required!</ng-container>
<ng-container *ngIf="!multi">Recipient is required!</ng-container>
</ng-template>
</evv-chips-autocomplete>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment