12/14/16

Creating Material Chips Input For Angular2

What Are Those Chips I'm Talking About

Here are some of many implementations that can be found across the web:
materializecss
angularjs material
Look at this gif that demonstrates chips behavior:

Angular2 Chips

Recently I looked on Angular Material2 repository that being developed quickly those days. I noticed that currently the chips ui is not developed yet, So i decided to try to do it myself and here i want to share some of my thoughts about this challenge of mine...

References Of Experts

During my research i was helped reading by those great articles of those 3 great guys: implementing-angular2-forms-beyond-basics
linkup-custom-control-to-ngcontrol-ngmodel
understanding-controlvalueaccessor

First Way

We may make some component that gets chips array as two way binding parameter from its parent
HTML:


   //in parent component:
   values:string[]=['lolo']
    ...
   <chips [chips]="values"></chips>

chipsComponent.ts:

import {Component, Input, Output, EventEmitter} from '@angular/core'
@Component({
  selector: 'chips',
  template: `
   <div *ngIf="values">
      <span *ngFor="let value of values" style="font-size:14px"
          class="label label-default" (click)="removeValue(tag)">
        {{value}} <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
      </span>
      <span> | </span>
      <span style="display:inline-block;">
        <input [(ngModel)]="labelToAdd" style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-plus" aria-hidden="true" (click)="addValue(valueToAdd)"></em>
      </span>
    </div>
  `
})
export class Chips { 
  @Input()
  get chips() {
    return this.values;
  }
  set chips(val) {
    this.values = val;
    this.labelsChange.emit(this.values);
  }

  @Output()
  labelsChange: EventEmitter = new EventEmitter();

  removeValue(value:string) {
    var index = this.values.indexOf(value, 0);
    if (index != undefined) {
      this.values.splice(index, 1);
      this.labelsChange.emit(this.values);
    }
  }

  addValue(value:string) {
    this.values.push(this.labelToAdd);
    this.labelsChange.emit(this.values);
    this.labelToAdd = '';
  }
}

see this code runs in plunker

Second Way (using Angular2 Forms)

My goal was to create a component/driective that will use ngModel directive to update the model:



 <material-chips [(ngModel)]="tags" ></material-chips>


For this we need to implement valueAccessor interface:

import {Component, NgModel, NgModule, OnInit, Input, Output, EventEmitter, ElementRef, forwardRef} from '@angular/core'

import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';


const noop = () => {
};

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => Chips),
    multi: true
};

@Component({
  selector: 'chips',
  template: `
    <div *ngIf="values">
      <span *ngFor="let value of values" style="font-size:14px"
          class="label label-default" (click)="removeValue(tag)">
        {{value}} <span class="glyphicon glyphicon-remove" aria-hidden="true"></span>
      </span>
      <span> | </span>
      <span style="display:inline-block;">
        <input [(ngModel)]="labelToAdd" style="width: 50px; font-size: 14px;" class="custom"/>
        <em class="glyphicon glyphicon-plus" aria-hidden="true" (click)="addValue(valueToAdd)"></em>
      </span>
    </div>
  `,
   providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR] 
})
export class Chips  implements ControlValueAccessor { 
  //by the Control Value Accessor
  private onTouchedCallback: () => void = noop;
  private onChangeCallback: (_: any) => void = noop;
  registerOnChange(fn: any) { this.onChangeCallback = fn;}
  registerOnTouched(fn: any) { this.onTouchedCallback = fn;}

  @Output()
  labelsChange: EventEmitter = new EventEmitter();

  removeValue(value:string) {
    var index = this.values.indexOf(value, 0);
    if (index != undefined) {
      this.values.splice(index, 1);
      this.labelsChange.emit(this.values);
    }
  }

  addValue(value:string) {
    if(!this.labelToAdd || this.labelToAdd.trim()===''){return;}
    this.values.push(this.labelToAdd);
    this.labelsChange.emit(this.values);
    this.labelToAdd = '';
  }
  
  //From ControlValueAccessor interface
  writeValue(value: any) {
      if (value !== this.values) {
          this.values = value;
      }
  } 

}


Getting started with docker

It is very simple to get started usig docker. All you need to do-is download the docker desktop for your system Once you get docker syste...