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 plunkerSecond 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;
}
}
}