@ngxform/ng-bootstrap-typeahead

Add bootstrap typeahead control to NgxForm

Usage no npm install needed!

<script type="module">
  import ngxformNgBootstrapTypeahead from 'https://cdn.skypack.dev/@ngxform/ng-bootstrap-typeahead';
</script>

README

Vicoders NgxForm Bootstrap Typeahead

Add bootstrap typeahead control to NgxForm

Install

Install package

yarn add @ngxform/ng-bootstrap-typeahead

Import module

import { NgxFormTypeaheadModule } from '@ngxform/ng-bootstrap-typeahead';
...

@NgModule({
  ...
  imports: [
    FormsModule,
    ReactiveFormsModule,
    NgxFormModule,
    NgxFormTypeaheadModule
  ],
})

Input Option

interface NgxBootstrapTypeaheadOption extends NgxFormControlOption {
  defaultValue?: any;
  hostClass?: any;
  ngClass?: any;
  options?: any[];
  ngbTypeahead?: (text: Observable<string>) => Observable<readonly any[]>;
  resultTemplate?: TemplateRef<ResultTemplateContext>;
  windowTemplate?: TemplateRef<WindowTemplateContext>;
  inputFormatter?: (item: any) => string;
  openOnFocus?: boolean;
  focus?: boolean | Subject<boolean>;
  disabled?: boolean;
  fullWithWindow?: boolean;
}

Usage

Simple typeahead

simple-typeahead.component.html

<form [formGroup]="demoForm" (ngSubmit)="onSubmit()">
  <ng-template ngxFormAnchor [controls]="demoForm.controls"></ng-template>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

simple-typeahead.component.ts

import { NgxBootstrapTypeaheadControl } from '@ngxform/ng-bootstrap-typeahead';
import { Component, OnInit } from '@angular/core';
import { NgxFormGroup } from '@ngxform/platform';
import { Validators } from '@angular/forms';

@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.scss']
})
export class SimpleTypeaheadComponent implements OnInit {
  public demoForm: NgxFormGroup;

  ngOnInit(): void {
    const typeaheadOptions: { name: string; flag: string }[] = [
      { name: 'Alabama', flag: '5/5c/Flag_of_Alabama.svg/45px-Flag_of_Alabama.svg.png' },
      { name: 'Alaska', flag: 'e/e6/Flag_of_Alaska.svg/43px-Flag_of_Alaska.svg.png' },
      { name: 'Arizona', flag: '9/9d/Flag_of_Arizona.svg/45px-Flag_of_Arizona.svg.png' },
      { name: 'Arkansas', flag: '9/9d/Flag_of_Arkansas.svg/45px-Flag_of_Arkansas.svg.png' },
      { name: 'California', flag: '0/01/Flag_of_California.svg/45px-Flag_of_California.svg.png' },
      { name: 'Colorado', flag: '4/46/Flag_of_Colorado.svg/45px-Flag_of_Colorado.svg.png' },
      { name: 'Connecticut', flag: '9/96/Flag_of_Connecticut.svg/39px-Flag_of_Connecticut.svg.png' },
      { name: 'Delaware', flag: 'c/c6/Flag_of_Delaware.svg/45px-Flag_of_Delaware.svg.png' },
      { name: 'Florida', flag: 'f/f7/Flag_of_Florida.svg/45px-Flag_of_Florida.svg.png' }
    ];
    this.demoForm = new NgxFormGroup({
      typeahead: new NgxBootstrapTypeaheadControl('', [Validators.required, Validators.email, Validators.minLength(3)], [], {
        label: 'NgBootstrap Typeahead',
        controlClass: ['form-control'],
        ngClass: 'd-flex flex-column form-group',
        options: typeaheadOptions,
        inputFormatter: (item: any) => item.name,
        openOnFocus: true,
        errorMessages: [
          { key: 'required', message: 'This field is required' },
        ]
      })
    });
  }

  onSubmit(): void {
    this.demoForm.submitted = true;
    console.log(this.demoForm.value);
  }
}

Custom template

Custom Result Template

custom-result-template.component.html

<ng-template #resultTemplate let-r="result" let-t="term">
  <img [src]="'https://upload.wikimedia.org/wikipedia/commons/thumb/' + r['flag']" class="mr-1" style="width: 16px">
  {{r.name}}
</ng-template>
<form [formGroup]="demoForm" (ngSubmit)="onSubmit()">
  <ng-template ngxFormAnchor [controls]="demoForm.controls"></ng-template>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

custom-result-template.component.ts

import { NgxBootstrapTypeaheadControl, ResultTemplateContext } from '@ngxform/ng-bootstrap-typeahead';
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { NgxFormGroup } from '@ngxform/platform';
import { Validators } from '@angular/forms';

@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.scss']
})
export class CustomResultTemplateComponent implements OnInit {
  public demoForm: NgxFormGroup;

  @ViewChild('resultTemplate', { static: true }) resultTemplate: TemplateRef<ResultTemplateContext>;

  ngOnInit(): void {
    const typeaheadOptions: { name: string; flag: string }[] = [
      { name: 'Alabama', flag: '5/5c/Flag_of_Alabama.svg/45px-Flag_of_Alabama.svg.png' },
      { name: 'Alaska', flag: 'e/e6/Flag_of_Alaska.svg/43px-Flag_of_Alaska.svg.png' },
      { name: 'Arizona', flag: '9/9d/Flag_of_Arizona.svg/45px-Flag_of_Arizona.svg.png' },
      { name: 'Arkansas', flag: '9/9d/Flag_of_Arkansas.svg/45px-Flag_of_Arkansas.svg.png' },
      { name: 'California', flag: '0/01/Flag_of_California.svg/45px-Flag_of_California.svg.png' },
      { name: 'Colorado', flag: '4/46/Flag_of_Colorado.svg/45px-Flag_of_Colorado.svg.png' },
      { name: 'Connecticut', flag: '9/96/Flag_of_Connecticut.svg/39px-Flag_of_Connecticut.svg.png' },
      { name: 'Delaware', flag: 'c/c6/Flag_of_Delaware.svg/45px-Flag_of_Delaware.svg.png' },
      { name: 'Florida', flag: 'f/f7/Flag_of_Florida.svg/45px-Flag_of_Florida.svg.png' }
    ];
    this.demoForm = new NgxFormGroup({
      typeahead: new NgxBootstrapTypeaheadControl('', [Validators.required, Validators.email, Validators.minLength(3)], [], {
        label: 'NgBootstrap Typeahead',
        controlClass: ['form-control'],
        ngClass: 'd-flex flex-column form-group',
        options: typeaheadOptions,
        resultTemplate: this.resultTemplate,
        inputFormatter: (item: any) => item.name,
        openOnFocus: true,
        errorMessages: [
          { key: 'required', message: 'This field is required' },
        ]
      })
    });
  }

  onSubmit(): void {
    this.demoForm.submitted = true;
    console.log(this.demoForm.value);
  }
}

Custom Window Template

custom-window-template.component.html

<ng-template #windowTemplate let-results="results" let-term="term" let-formatter="formatter" let-markActive="markActive" let-select="select" let-activeIdx="activeIdx">
  <ng-template #rt let-r="result" let-t="term">
    <img [src]="'https://upload.wikimedia.org/wikipedia/commons/thumb/' + r['flag']" class="mr-1" style="width: 16px" />
    {{ r.name }}
  </ng-template>
  <ng-template ngFor [ngForOf]="results" let-result let-idx="index">
    <button type="button" class="dropdown-item" role="option" [id]="id + '-' + idx" [class.active]="idx === activeIdx" (mouseenter)="markActive(idx)" (click)="select(result)">
      <ng-template [ngTemplateOutlet]="resultTemplate || rt" [ngTemplateOutletContext]="{ result: result, term: term, formatter: formatter }"></ng-template>
    </button>
  </ng-template>
</ng-template>

<form [formGroup]="demoForm" (ngSubmit)="onSubmit()">
  <ng-template ngxFormAnchor [controls]="demoForm.controls"></ng-template>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

custom-window-template.component.ts


import { NgxBootstrapTypeaheadControl, WindowTemplateContext } from '@ngxform/ng-bootstrap-typeahead';
import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { NgxFormGroup } from '@ngxform/platform';
import { Validators } from '@angular/forms';

@Component({
  selector: 'app-basic',
  templateUrl: './basic.component.html',
  styleUrls: ['./basic.component.scss']
})
export class CustomWindowTemplateComponent implements OnInit {
  public demoForm: NgxFormGroup;

  @ViewChild('windowTemplate', { static: true }) windowTemplate: TemplateRef<WindowTemplateContext>;

  ngOnInit(): void {
    const typeaheadOptions: { name: string; flag: string }[] = [
      { name: 'Alabama', flag: '5/5c/Flag_of_Alabama.svg/45px-Flag_of_Alabama.svg.png' },
      { name: 'Alaska', flag: 'e/e6/Flag_of_Alaska.svg/43px-Flag_of_Alaska.svg.png' },
      { name: 'Arizona', flag: '9/9d/Flag_of_Arizona.svg/45px-Flag_of_Arizona.svg.png' },
      { name: 'Arkansas', flag: '9/9d/Flag_of_Arkansas.svg/45px-Flag_of_Arkansas.svg.png' },
      { name: 'California', flag: '0/01/Flag_of_California.svg/45px-Flag_of_California.svg.png' },
      { name: 'Colorado', flag: '4/46/Flag_of_Colorado.svg/45px-Flag_of_Colorado.svg.png' },
      { name: 'Connecticut', flag: '9/96/Flag_of_Connecticut.svg/39px-Flag_of_Connecticut.svg.png' },
      { name: 'Delaware', flag: 'c/c6/Flag_of_Delaware.svg/45px-Flag_of_Delaware.svg.png' },
      { name: 'Florida', flag: 'f/f7/Flag_of_Florida.svg/45px-Flag_of_Florida.svg.png' }
    ];
    this.demoForm = new NgxFormGroup({
      typeahead: new NgxBootstrapTypeaheadControl('', [Validators.required, Validators.email, Validators.minLength(3)], [], {
        label: 'NgBootstrap Typeahead',
        controlClass: ['form-control'],
        ngClass: 'd-flex flex-column form-group',
        options: typeaheadOptions,
        inputFormatter: (item: any) => item.name,
        openOnFocus: true,
        errorMessages: [
          { key: 'required', message: 'This field is required' },
          { key: 'email', message: 'Email is invalid' },
          { key: 'minlength', message: 'Min length is 3' }
        ]
      })
    });
  }

  onSubmit(): void {
    this.demoForm.submitted = true;
    console.log(this.demoForm.value);
  }
}


Dynamic data

dynamic.component.html

<form [formGroup]="demoForm" (ngSubmit)="onSubmit()">
  <ng-template ngxFormAnchor [controls]="demoForm.controls"></ng-template>
  <button type="submit" class="btn btn-primary">Submit</button>
</form>

dynamic.component.ts

import { Component, OnInit, TemplateRef, ViewChild } from '@angular/core';
import { NgxFormGroup } from '@ngxform/platform';
import { Validators } from '@angular/forms';
import { catchError, debounceTime, distinctUntilChanged, map, switchMap } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { HttpClient } from '@angular/common/http';
import { NgxBootstrapTypeaheadControl, WindowTemplateContext } from '@ngxform/ng-bootstrap-typeahead';

@Component({
  selector: 'app-dynamic',
  templateUrl: './dynamic.component.html',
  styleUrls: ['./dynamic.component.scss']
})
export class DynamicComponent implements OnInit {
  public demoForm: NgxFormGroup;
  @ViewChild('windowTemplate', { static: true }) windowTemplate: TemplateRef<WindowTemplateContext>;

  constructor(private http: HttpClient) {}

  ngOnInit(): void {
    const search = (text$: Observable<string>) =>
      text$.pipe(
        debounceTime(200),
        distinctUntilChanged(),
        switchMap((term) => {
          if (term !== undefined && term !== '') {
            return this.http.get(`https://restcountries.eu/rest/v2/name/${term.toLowerCase()}`).pipe(catchError((error) => error));
          } else {
            return this.http.get('https://restcountries.eu/rest/v2/all').pipe(catchError((error) => error));
          }
        }),
        map((data: any[]) => {
          return data.map((item) => Object.assign(item, { key: item.name }));
        })
      );
    this.demoForm = new NgxFormGroup({
      typeahead: new NgxBootstrapTypeaheadControl('', [Validators.required, Validators.email, Validators.minLength(3)], [], {
        label: 'NgBootstrap Typeahead',
        controlClass: ['form-control'],
        ngClass: 'd-flex flex-column form-group',
        inputFormatter: (item: any) => item.name,
        ngbTypeahead: search,
        errorMessages: [
          { key: 'required', message: 'This field is required' },
          { key: 'email', message: 'Email is invalid' },
          { key: 'minlength', message: 'Min length is 3' }
        ]
      })
    });
  }

  onSubmit(): void {
    this.demoForm.submitted = true;
    console.log(this.demoForm.value);
  }
}