diff --git a/public/docs/_examples/cb-select-box-component/e2e-spec.ts b/public/docs/_examples/cb-select-box-component/e2e-spec.ts
new file mode 100644
index 0000000000..768631bd88
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/e2e-spec.ts
@@ -0,0 +1,164 @@
+///
+'use strict';
+
+const heroes = [
+ {'id': 11, 'name': 'Mr. Nice'},
+ {'id': 12, 'name': 'Narco'},
+ {'id': 13, 'name': 'Bombasto'},
+ {'id': 14, 'name': 'Celeritas'},
+ {'id': 15, 'name': 'Magneta'},
+ {'id': 16, 'name': 'RubberMan'},
+ {'id': 17, 'name': 'Dynama'},
+ {'id': 18, 'name': 'Dr IQ'},
+ {'id': 19, 'name': 'Magma'},
+ {'id': 20, 'name': 'Tornado'}
+];
+
+describe('Select box component', function () {
+
+ beforeAll(function () {
+ browser.get('');
+ });
+
+ describe('Verbose select', function () {
+ let verboseComponent: protractor.ElementFinder;
+ let firstSelect: protractor.ElementFinder;
+
+ beforeEach(function () {
+ verboseComponent = element.all(by.tagName('my-select-verbose')).get(0);
+ firstSelect = verboseComponent.all(by.tagName('select')).get(0);
+ });
+
+ it('should show a selected hero both in the select and outside it', function () {
+ firstSelect.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Narco');
+ });
+
+ const heroOnPTag = verboseComponent.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Narco');
+ });
+
+ it('should change the selected hero on option change', function () {
+ firstSelect.all(by.cssContainingText('option', 'Bombasto')).first().click();
+
+ firstSelect.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Bombasto');
+ });
+
+ const heroOnPTag = verboseComponent.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Bombasto');
+ });
+
+ it('should change select collection on button clicks', function () {
+ const reloadButton = verboseComponent.all(by.buttonText('Reload'));
+ const clearButton = verboseComponent.all(by.buttonText('Clear'));
+ const removeButton = verboseComponent.all(by.buttonText('Remove'));
+
+ let options = firstSelect.all(by.tagName('option'));
+ expect(options.count()).toBe(10);
+
+ removeButton.click().then(function () {
+ options = firstSelect.all(by.tagName('option'));
+ expect(options.count()).toBe(9);
+
+ clearButton.click().then(function () {
+ options = firstSelect.all(by.tagName('option'));
+ expect(options.count()).toBe(0);
+
+ reloadButton.click().then(function () {
+ options = firstSelect.all(by.tagName('option'));
+ expect(options.count()).toBe(10);
+ });
+ });
+ });
+ });
+ });
+
+ describe('First selector', function () {
+ let selectorComponent: protractor.ElementFinder;
+ let firstSelector: protractor.ElementFinder;
+
+ beforeEach(function () {
+ selectorComponent = element.all(by.tagName('my-selector-host')).get(0);
+ firstSelector = selectorComponent.all(by.tagName('select')).get(0);
+ });
+
+ it('should show a selected hero both in the select and outside it', function () {
+ firstSelector.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Mr. Nice');
+ });
+
+ const firstSelectorOutput = selectorComponent.all(by.tagName('div')).get(0);
+ const heroOnPTag = firstSelectorOutput.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Mr. Nice');
+ });
+
+ it('should change the selected hero on option change', function () {
+ firstSelector.all(by.cssContainingText('option', 'Bombasto')).first().click();
+
+ firstSelector.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Bombasto');
+ });
+
+ const firstSelectorOutput = selectorComponent.all(by.tagName('div')).get(0);
+ const heroOnPTag = firstSelectorOutput.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Bombasto');
+ });
+
+ it('should change select collection on button clicks', function () {
+ const reloadButton = selectorComponent.all(by.buttonText('Reload'));
+ const clearButton = selectorComponent.all(by.buttonText('Clear'));
+ const removeButton = selectorComponent.all(by.buttonText('Remove'));
+
+ let options = firstSelector.all(by.tagName('option'));
+ expect(options.count()).toBe(10);
+
+ removeButton.click().then(function () {
+ options = firstSelector.all(by.tagName('option'));
+ expect(options.count()).toBe(9);
+
+ clearButton.click().then(function () {
+ options = firstSelector.all(by.tagName('option'));
+ expect(options.count()).toBe(0);
+
+ reloadButton.click().then(function () {
+ options = firstSelector.all(by.tagName('option'));
+ expect(options.count()).toBe(10);
+ });
+ });
+ });
+ });
+ });
+
+ describe('Second selector', function () {
+ let selectorComponent: protractor.ElementFinder;
+ let secondSelector: protractor.ElementFinder;
+
+ beforeEach(function () {
+ selectorComponent = element.all(by.tagName('my-selector-host')).get(0);
+ secondSelector = selectorComponent.all(by.tagName('select')).get(1);
+ });
+
+ it('should show a selected hero both in the select and outside it', function () {
+ secondSelector.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Narco');
+ });
+
+ const secondSelectorOutput = selectorComponent.all(by.tagName('div')).get(1);
+ const heroOnPTag = secondSelectorOutput.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Narco');
+ });
+
+ it('should change the selected hero on option change', function () {
+ secondSelector.all(by.cssContainingText('option', 'Bombasto')).first().click();
+
+ secondSelector.getAttribute('value').then(function (hero: any) {
+ expect(heroes[hero].name).toEqual('Bombasto');
+ });
+
+ const secondSelectorOutput = selectorComponent.all(by.tagName('div')).get(1);
+ const heroOnPTag = secondSelectorOutput.all(by.tagName('p')).last().getText();
+ expect(heroOnPTag).toContain('Bombasto');
+ });
+ });
+});
diff --git a/public/docs/_examples/cb-select-box-component/ts/.gitignore b/public/docs/_examples/cb-select-box-component/ts/.gitignore
new file mode 100644
index 0000000000..cf44e148ba
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/.gitignore
@@ -0,0 +1 @@
+**/*.js
\ No newline at end of file
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts
new file mode 100644
index 0000000000..a9a744d2e8
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/app.component.1.ts
@@ -0,0 +1,12 @@
+// #docregion
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'my-app',
+ template: `
+
Favorite Hero
+
+ `
+})
+export class AppComponent {}
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts
new file mode 100644
index 0000000000..7d79b3b28a
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/app.component.ts
@@ -0,0 +1,14 @@
+// #docregion
+import { Component } from '@angular/core';
+
+@Component({
+ selector: 'my-app',
+ template: `
+ Favorite Hero
+
+
+
+ `
+})
+export class AppComponent {}
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts b/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts
new file mode 100644
index 0000000000..f3ab50871c
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/app.module.ts
@@ -0,0 +1,21 @@
+import { NgModule } from '@angular/core';
+import { BrowserModule } from '@angular/platform-browser';
+
+import { AppComponent } from './app.component';
+import { SelectVerboseComponent } from './select-verbose.component';
+import { SelectorHostComponent } from './selector-host.component';
+import { SelectorComponent } from './selector.component';
+import { HeroStoreService } from './hero-store.service';
+
+@NgModule({
+ imports: [ BrowserModule ],
+ declarations: [
+ AppComponent,
+ SelectVerboseComponent,
+ SelectorHostComponent,
+ SelectorComponent
+ ],
+ providers: [ HeroStoreService ],
+ bootstrap: [ AppComponent ]
+})
+export class AppModule {}
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts b/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts
new file mode 100644
index 0000000000..5d88f770a8
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/hero-store.service.ts
@@ -0,0 +1,26 @@
+// #docregion
+import { Injectable } from '@angular/core';
+
+export interface Hero {
+ id: number;
+ name: string;
+}
+
+@Injectable()
+export class HeroStoreService {
+ get heroes(): Hero[] { return HeroStoreService.heroCache; }
+
+ static heroCache: Hero[] = [
+ {'id': 11, 'name': 'Mr. Nice'},
+ {'id': 12, 'name': 'Narco'},
+ {'id': 13, 'name': 'Bombasto'},
+ {'id': 14, 'name': 'Celeritas'},
+ {'id': 15, 'name': 'Magneta'},
+ {'id': 16, 'name': 'RubberMan'},
+ {'id': 17, 'name': 'Dynama'},
+ {'id': 18, 'name': 'Dr IQ'},
+ {'id': 19, 'name': 'Magma'},
+ {'id': 20, 'name': 'Tornado'}
+ ];
+}
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/main.ts b/public/docs/_examples/cb-select-box-component/ts/app/main.ts
new file mode 100644
index 0000000000..9be7775f4d
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/main.ts
@@ -0,0 +1,5 @@
+import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
+
+import { AppModule } from './app.module';
+
+platformBrowserDynamic().bootstrapModule(AppModule);
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html
new file mode 100644
index 0000000000..629e2e1008
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.html
@@ -0,0 +1,14 @@
+
+
+
Select verbose: List of heroes
+
+
+
+
+
Selected Hero
+
{{selectedHero | json }}
+
Selected hero name = {{selectedHero?.name}}
+
+
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts
new file mode 100644
index 0000000000..d0589b63be
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.1.ts
@@ -0,0 +1,21 @@
+// #docregion
+import { Component, OnInit } from '@angular/core';
+
+import { Hero, HeroStoreService } from './hero-store.service';
+
+@Component({
+ selector: 'my-select-verbose',
+ templateUrl: 'app/select-verbose.component.html'
+})
+export class SelectVerboseComponent implements OnInit {
+ heroes: Hero[] = [];
+ selectedHero: Hero;
+
+ constructor(private store: HeroStoreService) { }
+
+ ngOnInit(): void {
+ this.heroes = this.store.heroes.slice();
+ this.selectedHero = this.heroes[1];
+ }
+}
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html
new file mode 100644
index 0000000000..5046aac17f
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.html
@@ -0,0 +1,22 @@
+
+
+
+
Select verbose: List of heroes
+
+
+
+
+
Selected Hero
+
{{selectedHero | json }}
+
Selected hero name = {{selectedHero?.name}}
+
+
+
+
+
+
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts
new file mode 100644
index 0000000000..f0808e9dbb
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/select-verbose.component.ts
@@ -0,0 +1,52 @@
+// #docplaster
+// #docregion
+import { Component, OnInit } from '@angular/core';
+
+import { Hero, HeroStoreService } from './hero-store.service';
+
+@Component({
+ selector: 'my-select-verbose',
+ templateUrl: 'app/select-verbose.component.html'
+})
+export class SelectVerboseComponent implements OnInit {
+ heroes: Hero[];
+ selectedHero: Hero;
+
+ constructor(private store: HeroStoreService) { }
+ // #docregion buttons-methods
+ clear(): void {
+ this.heroes = undefined;
+ this.selectedHero = undefined;
+ }
+ // #enddocregion buttons-methods
+ // #docregion select-methods
+ isSelected(hero: Hero): boolean {
+ return this.selectedHero && hero.id === this.selectedHero.id;
+ }
+ // #enddocregion select-methods
+ // #docregion buttons-methods
+ ngOnInit(): void {
+ this.reload();
+ this.selectedHero = this.heroes[1];
+ }
+ // #enddocregion buttons-methods
+ // #docregion select-methods
+ onChange(index: string): void {
+ this.selectedHero = this.heroes[+index];
+ }
+ // #enddocregion select-methods
+ // #docregion buttons-methods
+ reload(): void {
+ this.heroes = this.store.heroes.slice();
+ this.selectedHero = this.heroes[0];
+ }
+ removeSelected(): void {
+ if (this.heroes && this.selectedHero) {
+ const index = this.heroes.indexOf(this.selectedHero);
+ this.heroes.splice(index, 1);
+ this.selectedHero = this.heroes[1];
+ }
+ }
+ // #enddocregion buttons-methods
+}
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html
new file mode 100644
index 0000000000..3db2bf3ef3
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.html
@@ -0,0 +1,39 @@
+
+
+SelectedHero Selector - [key] (change)
+
+
+
+
+
+
Selected Hero
+
{{selectedHero | json}}
+
Selected hero name = {{selectedHero?.name}}
+
+
+
+
+
+
+
+
+
+
+Fan.Hero Selector - [(key)]
+
+
+
+
+
+
+
Hero Fan
+
{{heroFan | json}}
+
Fan's hero name = {{heroFan?.heroName}}
+
+
+
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts
new file mode 100644
index 0000000000..21785b9255
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/selector-host.component.ts
@@ -0,0 +1,60 @@
+// #docregion
+import { Component, OnInit } from '@angular/core';
+import { Hero, HeroStoreService } from './hero-store.service';
+
+// #docregion hero-fan
+class HeroFan {
+ constructor(
+ public id = 0,
+ public name: string,
+ public heroId?: number) {
+ }
+
+ get heroName(): string {
+ let hero = this.heroId &&
+ // DEMO ONLY: Do NOT do it this way
+ HeroStoreService.heroCache
+ .filter(h => h.id === this.heroId)[0];
+ return hero ? hero.name : '';
+ }
+}
+// #enddocregion hero-fan
+
+// #docregion component
+@Component({
+ selector: 'my-selector-host',
+ templateUrl: 'app/selector-host.component.html'
+})
+export class SelectorHostComponent implements OnInit {
+ heroes: Hero[];
+ heroFan = new HeroFan(42, 'Lucy');
+ selectedHero: Hero;
+
+ constructor(private store: HeroStoreService) { }
+
+ ngOnInit(): void {
+ this.reload();
+ this.selectedHero = this.heroes[0];
+ this.heroFan.heroId = this.heroes[1].id;
+ }
+
+ clear(): void {
+ this.heroes = undefined;
+ this.selectedHero = undefined;
+ }
+
+ reload(): void {
+ this.heroes = this.store.heroes.slice(0);
+ this.selectedHero = this.heroes[0];
+ }
+
+ removeSelected(): void {
+ if (this.heroes && this.selectedHero) {
+ let index = this.heroes.indexOf(this.selectedHero);
+ this.heroes.splice(index, 1);
+ this.selectedHero = this.heroes[0];
+ }
+ }
+}
+// #enddocregion component
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts b/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts
new file mode 100644
index 0000000000..9fc466603c
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/app/selector.component.ts
@@ -0,0 +1,47 @@
+// #docplaster
+// #docregion
+// #docregion skeleton
+import { Component, EventEmitter, Input, Output } from '@angular/core';
+
+// #docregion next-id
+let nextSelectId = 1;
+// #enddocregion next-id
+
+@Component({
+ selector: 'my-selector',
+ exportAs: 'mySelector',
+ template: `
+
+ `
+})
+// #docregion selector-class
+export class SelectorComponent {
+// #enddocregion skeleton
+ @Input('id') selectId = `select-${nextSelectId++}`;
+ @Input() display = 'name';
+ @Input() key: any;
+ @Input() options: any[];
+ @Input() optionKey = 'id';
+
+ @Output() optionChange = new EventEmitter();
+ @Output() keyChange = new EventEmitter();
+
+ isSelected(option: any): boolean {
+ return this.key && this.options && option[this.optionKey] === this.key;
+ }
+
+ onChange(index: string): void {
+ this.optionChange.emit(this.options[+index]);
+ this.keyChange.emit(this.options[+index][this.optionKey]);
+ }
+// #docregion skeleton
+}
+// #enddocregion skeleton
+// #enddocregion selector-class
+// #enddocregion
diff --git a/public/docs/_examples/cb-select-box-component/ts/demo.css b/public/docs/_examples/cb-select-box-component/ts/demo.css
new file mode 100644
index 0000000000..8c50c5cdad
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/demo.css
@@ -0,0 +1,12 @@
+pre {
+ background: #EEE;
+ padding: 4px
+}
+
+select {
+ width: 100px;
+}
+
+button {
+ width: 75px;
+}
\ No newline at end of file
diff --git a/public/docs/_examples/cb-select-box-component/ts/example-config.json b/public/docs/_examples/cb-select-box-component/ts/example-config.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/docs/_examples/cb-select-box-component/ts/index.html b/public/docs/_examples/cb-select-box-component/ts/index.html
new file mode 100644
index 0000000000..21b7076801
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/index.html
@@ -0,0 +1,29 @@
+
+
+
+
+ Select box component
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Loading app...
+
+
+
diff --git a/public/docs/_examples/cb-select-box-component/ts/plnkr.json b/public/docs/_examples/cb-select-box-component/ts/plnkr.json
new file mode 100644
index 0000000000..e88c53fd4c
--- /dev/null
+++ b/public/docs/_examples/cb-select-box-component/ts/plnkr.json
@@ -0,0 +1,9 @@
+{
+ "description": "Select Box Component",
+ "files":[
+ "!**/*.d.ts",
+ "!**/*.js",
+ "!**/*.[1].*"
+ ],
+ "tags":"[cookbook]"
+}
\ No newline at end of file
diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json
index f82d816cef..86cbcc80df 100644
--- a/public/docs/ts/latest/cookbook/_data.json
+++ b/public/docs/ts/latest/cookbook/_data.json
@@ -48,6 +48,11 @@
"intro": "Migrate your RC4 app to RC5 in minutes."
},
+ "select-box-component": {
+ "title": "Select Box Component",
+ "intro": "Building a custom select box"
+ },
+
"set-document-title": {
"title": "Set the Document Title",
"intro": "Setting the document or window title using the Title service."
diff --git a/public/docs/ts/latest/cookbook/select-box-component.jade b/public/docs/ts/latest/cookbook/select-box-component.jade
new file mode 100644
index 0000000000..8d55975f4e
--- /dev/null
+++ b/public/docs/ts/latest/cookbook/select-box-component.jade
@@ -0,0 +1,167 @@
+include ../_util-fns
+
+:marked
+ We definitely need to see which hero is our favorite one, we are going to build a select box and then refactor it into a component.
+
+
+ ## Table of contents
+
+ [A heroes service](#hero-service)
+
+ [A verbose component](#verbose-component)
+
+ [Refactoring the select box into a component](#refactor)
+
+ [Using the new component](#using)
+
+:marked
+ **See the [live example](/resources/live-examples/cb-select-box-component/ts/plnkr.html)**.
+
+.l-main-section
+
+:marked
+ ## A heroes service
+
+ We need heroes, lots of heroes. Let's use a service.
+
++makeExample('cb-select-box-component/ts/app/hero-store.service.ts', null, 'hero-store.service.ts')(format=".")
+
+
+:marked
+ ## A verbose component
+
+ Our first implementation involves creating a `