import {Component} from 'angular2/core';
import {TagInputComponent} from './tag-input.component';
@Component({
selector: 'my-app',
template: `
<h1>Angular 2 Tag Input Example</h1>
<h2>Basic Example</h2>
<tag-input
placeholder="Add an tag"
[(ngModel)]="settings.tags"
delimiterCode="188">
</tag-input>
<br><br><br>
<h2>Example with email validation</h2>
<tag-input
placeholder="Add an email"
[allowedTagsPattern]="validEmailPattern"
[(ngModel)]="settings.recipients"
delimiterCode="188">
</tag-input>
<br><br><br>
`,
directives: [TagInputComponent]
})
export class AppComponent {
public validEmailPattern = /^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
public settings = {
recipients: [],
tags: ['one', 'two', 'three']
};
}
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
import {bootstrap} from 'angular2/platform/browser';
import {AppComponent} from './app.component';
bootstrap(AppComponent);
/*
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
*/
html,
body {
font-family: arial;
height: 100%;
}
body {
display: flex;
flex-direction: column;
}
.main {
display: flex;
flex-direction: column;
flex: 1;
}
/**
* Forms
*/
.form-group {
margin-bottom: 20px;
}
label {
display: block;
}
input,
textarea {
padding: 5px;
border: 0;
box-shadow: 0 1px #ccc;
}
input:focus,
textarea:focus {
box-shadow: 0 2px #0d8bff;
outline: 0;
}
<!DOCTYPE html>
<html>
<head>
<title>Angular 2 QuickStart</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="styles.css">
<!-- 1. Load libraries -->
<!-- IE required polyfills, in this exact order -->
<script src="https://cdnjs.cloudflare.com/ajax/libs/es6-shim/0.35.0/es6-shim.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/systemjs/0.19.20/system-polyfills.js"></script>
<script src="https://npmcdn.com/angular2@2.0.0-beta.12/es6/dev/src/testing/shims_for_IE.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/angular2-polyfills.js"></script>
<script src="https://code.angularjs.org/tools/system.js"></script>
<script src="https://npmcdn.com/typescript@1.8.9/lib/typescript.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/Rx.js"></script>
<script src="https://code.angularjs.org/2.0.0-beta.12/angular2.dev.js"></script>
<!-- 2. Configure SystemJS -->
<script>
System.config({
transpiler: 'typescript',
typescriptOptions: { emitDecoratorMetadata: true },
packages: {'app': {defaultExtension: 'ts'}}
});
System.import('app/main')
.then(null, console.error.bind(console));
</script>
</head>
<!-- 3. Display the application -->
<body>
<my-app>Loading...</my-app>
</body>
</html>
<!--
Copyright 2016 Google Inc. All Rights Reserved.
Use of this source code is governed by an MIT-style license that
can be found in the LICENSE file at http://angular.io/license
-->
import {Component, HostBinding, Input, Output, Provider, forwardRef, EventEmitter} from 'angular2/core';
import {NgControl} from 'angular2/common';
import {isBlank} from 'angular2/src/facade/lang';
import {TagInputItemComponent} from './tag-input-item.component';
@Component({
selector: 'tag-input',
template:
`<tag-input-item
[text]="tag"
[index]="index"
[selected]="selectedTag === index"
(tagRemoved)="_removeTag($event)"
*ngFor="#tag of tagsList; #index = index">
</tag-input-item>
<input
class="ng2-tag-input-field"
type="text"
[placeholder]="placeholder"
[(ngModel)]="inputValue"
(paste)="inputPaste($event)"
(keydown)="inputChanged($event)"
(blur)="inputBlurred($event)"
(focus)="inputFocused()"
#tagInputRef>`,
styles: [`
:host {
display: block;
box-shadow: 0 1px #ccc;
padding: 5px 0;
}
:host.ng2-tag-input-focus {
box-shadow: 0 2px #0d8bff;
}
.ng2-tag-input-field {
box-shadow: none;
border: 0;
}
`],
directives: [TagInputItemComponent]
})
export class TagInputComponent {
@Input() placeholder: string = 'Add a tag';
@Input() ngModel: string[];
@Input() delimiterCode: string = '188';
@Input() addOnBlur: boolean = true;
@Input() addOnEnter: boolean = true;
@Input() addOnPaste: boolean = true;
@Input() allowedTagsPattern: RegExp = /.+/;
@HostBinding('class.ng2-tag-input-focus') isFocussed;
public tagsList: string[];
public inputValue: string = '';
public delimiter: number;
public selectedTag: number;
constructor(private _ngControl: NgControl) {
this._ngControl.valueAccessor = this;
}
ngOnInit() {
if (this.ngModel) this.tagsList = this.ngModel;
this.onChange(this.tagsList);
this.delimiter = parseInt(this.delimiterCode);
}
ngAfterViewInit() {
// If the user passes an undefined variable to ngModel this will warn
// and set the value to an empty array
if (!this.tagsList) {
console.warn('TagInputComponent was passed an undefined value in ngModel. Please make sure the variable is defined.');
this.tagsList = [];
this.onChange(this.tagsList);
}
}
inputChanged(event) {
let key = event.keyCode;
switch(key) {
case 8: // Backspace
this._handleBackspace();
break;
case 13: //Enter
this.addOnEnter && this._addTags([this.inputValue]);
event.preventDefault();
break;
case this.delimiter:
this._addTags([this.inputValue]);
event.preventDefault();
break;
default:
this._resetSelected();
break;
}
}
inputBlurred(event) {
this.addOnBlur && this._addTags([this.inputValue]);
this.isFocussed = false;
}
inputFocused(event) {
this.isFocussed = true;
}
inputPaste(event) {
let clipboardData = event.clipboardData || (event.originalEvent && event.originalEvent.clipboardData);
let pastedString = clipboardData.getData('text/plain');
let tags = this._splitString(pastedString);
let tagsToAdd = tags.filter((tag) => this._isTagValid(tag));
this._addTags(tagsToAdd);
setTimeout(() => this.inputValue = '', 3000);
}
private _splitString(tagString: string) {
tagString = tagString.trim();
let tags = tagString.split(String.fromCharCode(this.delimiter));
return tags.filter((tag) => !!tag);
}
private _isTagValid(tagString: string) {
return this.allowedTagsPattern.test(tagString);
}
private _addTags(tags: string[]) {
let validTags = tags.filter((tag) => this._isTagValid(tag));
this.tagsList = this.tagsList.concat(validTags);
this._resetSelected();
this._resetInput();
this.onChange(this.tagsList);
}
private _removeTag(tagIndexToRemove) {
this.tagsList.splice(tagIndexToRemove, 1);
this._resetSelected();
this.onChange(this.tagsList);
}
private _handleBackspace() {
if (!this.inputValue.length && this.tagsList.length) {
if (!isBlank(this.selectedTag)) {
this._removeTag(this.selectedTag);
}
else {
this.selectedTag = this.tagsList.length - 1;
}
}
}
private _resetSelected() {
this.selectedTag = null;
}
private _resetInput() {
this.inputValue = '';
}
/** Implemented as part of ControlValueAccessor. */
onChange: (value) => any = () => { };
onTouched: () => any = () => { };
writeValue(value: any) { }
registerOnChange(fn: any) {
this.onChange = fn;
}
registerOnTouched(fn: any) {
this.onTouched = fn;
}
}
import { Component, EventEmitter, Input, Output } from 'angular2/core';
@Component({
selector: 'tag-input-item',
template:
`{{text}}
<span
class="ng2-tag-input-remove"
(click)="removeTag()">×</span>`,
styles: [`
:host {
display: inline-block;
background: #ccc;
padding: 7px;
border-radius: 90px;
margin-right: 10px;
}
:host.ng2-tag-input-item-selected {
color: white;
background: #0d8bff;
}
.ng2-tag-input-remove {
cursor: pointer;
display: inline-block;
padding: 0 3px;
}
`],
host: {
'[class.ng2-tag-input-item-selected]': 'selected'
}
})
export class TagInputItemComponent {
@Input() selected: boolean;
@Input() text: string;
@Input() index: number;
@Output() tagRemoved: EventEmitter<number> = new EventEmitter();
constructor() { }
removeTag() {
this.tagRemoved.emit(this.index);
}
}