diff --git a/public/docs/_examples/cb-custom-form-controls/e2e-spec.ts b/public/docs/_examples/cb-custom-form-controls/e2e-spec.ts
new file mode 100644
index 0000000000..4121b7805b
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/e2e-spec.ts
@@ -0,0 +1,44 @@
+///
+'use strict';
+describe('Building Custom Form Controls With ngModel', function () {
+
+ beforeAll(function () {
+ browser.get('');
+ });
+
+ it('should all be off', function () {
+ const toggles = element.all(by.css('cb-toggle'));
+
+ expect(toggles.getAttribute('class')).toContain('for-off');
+ expect(toggles.getAttribute('class')).not.toContain('for-on');
+ });
+
+ it('should all toggle on', function () {
+ const toggle = element.all(by.css('cb-toggle')).first();
+
+ toggle.click();
+
+ const toggles = element.all(by.css('cb-toggle'));
+
+ expect(toggles.getAttribute('class')).toContain('for-on');
+ expect(toggles.getAttribute('class')).not.toContain('for-off');
+ });
+
+ it('should use ng-form classes that reflect interactions', function () {
+ const toggle = element.all(by.css('cb-toggle')).last();
+
+ expect(toggle.getAttribute('class')).toContain('ng-pristine');
+ expect(toggle.getAttribute('class')).not.toContain('ng-dirty');
+
+ toggle.click();
+
+ expect(toggle.getAttribute('class')).toContain('ng-dirty');
+ expect(toggle.getAttribute('class')).not.toContain('ng-pristine');
+
+ const toggles = element.all(by.css('cb-toggle'));
+
+ expect(toggles.getAttribute('class')).toContain('for-off');
+ expect(toggles.getAttribute('class')).not.toContain('for-on');
+ });
+
+});
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/.gitignore b/public/docs/_examples/cb-custom-form-controls/ts/.gitignore
new file mode 100644
index 0000000000..5c96d0d93b
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/.gitignore
@@ -0,0 +1 @@
+npm-debug.log
\ No newline at end of file
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/app/app.component.ts b/public/docs/_examples/cb-custom-form-controls/ts/app/app.component.ts
new file mode 100644
index 0000000000..ffc5ade9dc
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/app/app.component.ts
@@ -0,0 +1,31 @@
+// #docregion
+import { Component } from '@angular/core';
+// #docregion providers
+import { ToggleComponent } from './toggle.component';
+import { ToggleNgModelDirective } from './toggle-ng-model.directive';
+
+@Component({
+ selector: 'cb-app',
+ directives: [ ToggleComponent, ToggleNgModelDirective ],
+ template: `
+
+
+
+
+
+ `
+})
+// #enddocregion providers
+export class AppComponent {
+ public isOn = false;
+
+ public handleChange(newValue: boolean): void {
+ this.isOn = newValue;
+ }
+}
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/app/main.ts b/public/docs/_examples/cb-custom-form-controls/ts/app/main.ts
new file mode 100644
index 0000000000..cf659fb889
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/app/main.ts
@@ -0,0 +1,13 @@
+// #docregion
+import { bootstrap } from '@angular/platform-browser-dynamic';
+import { AppComponent } from './app.component';
+
+bootstrap(AppComponent).then(
+ function handleResolve() {
+ window.console.info( 'Angular finished bootstrapping your application!' )
+ },
+ function handleReject( error ) {
+ console.warn( 'Angular was not able to bootstrap your application.' );
+ console.error( error );
+ }
+);
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts b/public/docs/_examples/cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts
new file mode 100644
index 0000000000..7b6676c7d6
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts
@@ -0,0 +1,85 @@
+// #docregion
+import { ChangeDetectorRef, Directive, forwardRef, provide, SimpleChange } from '@angular/core';
+import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/common';
+
+// #docregion valueaccesor
+import { ToggleComponent } from './toggle.component';
+
+// #docregion provider
+@Directive({
+ selector: 'cb-toggle[ngControl],cb-toggle[ngFormControl],cb-toggle[ngModel]',
+ providers: [
+ provide(
+ NG_VALUE_ACCESSOR,
+ {
+ useExisting: forwardRef(() => ToggleNgModelDirective),
+ multi: true
+ }
+ )
+ ],
+ host: {
+ '(valueChange)': 'handleValueChange($event)'
+ }
+})
+export class ToggleNgModelDirective implements ControlValueAccessor {
+// #enddocregion provider
+
+ private changeCount = 0;
+ private onChange = noop;
+ private onTouched = noop;
+
+ constructor(private toggle: ToggleComponent, private changeDetectorRef: ChangeDetectorRef) {}
+
+ handleValueChange(newValue: boolean): void {
+ // When implementing ngModel, we are purposefully circumventing one-way data flow.
+ // As such, when the target component emits a change event, we want to turn around
+ // and pipe that change right back into the target component.
+ this.applyChangeToTarget(this.toggle.value, newValue);
+ this.onChange(newValue);
+ }
+
+ registerOnChange(newOnChange: any): void {
+ this.onChange = newOnChange;
+ }
+
+ registerOnTouched(newOnTouched: any): void {
+ // CAUTION: For this demo, we are not worrying about "touch" events.
+ this.onTouched = newOnTouched;
+ }
+
+ writeValue(newValue: any): void {
+ this.applyChangeToTarget(this.toggle.value, !! newValue );
+ }
+
+ // #docregion applychanges
+ private applyChangeToTarget(previousValue: boolean, currentValue: boolean): void {
+ // Pipe the value into the target and alert the change detector that inputs
+ // have been changed PROGRAMMATICALLY.
+ this.toggle.value = currentValue;
+ this.changeDetectorRef.markForCheck();
+
+ // Unfortunately, when we change the target component's inputs programmatically,
+ // Angular doesn't help us with the life-cycle methods. As such, we have to fill in
+ // the ngOnChanges() gap as the target component may be depending on it internally.
+ if (this.toggle.ngOnChanges) {
+ let change = new SimpleChange( previousValue, currentValue );
+
+ // Unfortunately, Angular uses a private token internally to determine if the
+ // given change is the "first" change (for the given value) in the component's
+ // life-cycle. Since the target component may be relying on the isFirstChange()
+ // method to work as expected, we have to patch it manually.
+ if (!this.changeCount++) {
+ change.isFirstChange = (() => true);
+ }
+
+ this.toggle.ngOnChanges({ value: change });
+ }
+ }
+ // #enddocregion applychanges
+
+}
+// #enddocregion valueaccesor
+
+function noop(): void {
+ // No-operation function.
+}
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/app/toggle.component.ts b/public/docs/_examples/cb-custom-form-controls/ts/app/toggle.component.ts
new file mode 100644
index 0000000000..ef478a3b56
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/app/toggle.component.ts
@@ -0,0 +1,37 @@
+// #docregion
+import { Component, EventEmitter, Input, OnChanges, Output } from '@angular/core';
+
+// #docregion component
+@Component({
+ selector: 'cb-toggle',
+ host: {
+ '[class.for-on]': 'value',
+ '[class.for-off]': '! value',
+ '(click)': 'handleClick($event)'
+ },
+ template: `
+
+
+
+ {{ ( value ? 'On' : 'Off' ) }}
+ `
+})
+export class ToggleComponent implements OnChanges {
+ @Input() public value = false;
+ @Output() public valueChange = new EventEmitter;
+
+ handleClick(event: any): void {
+ this.valueChange.next( ! this.value );
+ }
+
+ ngOnChanges(changes: any): void {
+ console.log(
+ 'Toggle changed from %s to %s during %s change.',
+ changes.value.previousValue,
+ changes.value.currentValue,
+ (changes.value.isFirstChange() ? 'first' : 'subsequent')
+ );
+ }
+
+}
+// #enddocregion component
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/example-config.json b/public/docs/_examples/cb-custom-form-controls/ts/example-config.json
new file mode 100644
index 0000000000..e69de29bb2
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/index.html b/public/docs/_examples/cb-custom-form-controls/ts/index.html
new file mode 100644
index 0000000000..29890cece7
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/index.html
@@ -0,0 +1,41 @@
+
+
+
+
+
+
+
+
+
+ Creating Custom Form Controls Using ngModel
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Creating Custom Form Controls Using ngModel
+
+
+ Loading app...
+
+
+
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/plnkr.json b/public/docs/_examples/cb-custom-form-controls/ts/plnkr.json
new file mode 100644
index 0000000000..5b7ad480a0
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/plnkr.json
@@ -0,0 +1,9 @@
+{
+ "description": "Creating Custom Form Controls Using ngModel",
+ "files": [
+ "!**/*.d.ts",
+ "!**/*.js",
+ "!**/*.[1].*"
+ ],
+ "tags": [ "cookbook" ]
+}
diff --git a/public/docs/_examples/cb-custom-form-controls/ts/sample.css b/public/docs/_examples/cb-custom-form-controls/ts/sample.css
new file mode 100644
index 0000000000..cb52df07d1
--- /dev/null
+++ b/public/docs/_examples/cb-custom-form-controls/ts/sample.css
@@ -0,0 +1,61 @@
+/* #docregion */
+cb-toggle {
+ cursor: pointer ;
+ display: table ;
+ height: 26px ;
+ line-height: 27px ;
+ margin-bottom: 16px ;
+ user-select: none ;
+ -moz-user-select: none ;
+ -webkit-user-select: none ;
+}
+
+cb-toggle div.switch {
+ background-color: #333333 ;
+ border-radius: 26px 26px 26px 26px ;
+ display: inline-block ;
+ height: 26px ;
+ overflow: hidden ;
+ position: relative ;
+ width: 55px ;
+}
+
+cb-toggle span.thumb {
+ background-color: #F0F0F0 ;
+ border-radius: 100px ;
+ height: 20px ;
+ left: 0px ;
+ margin-left: 4px ;
+ position: absolute ;
+ transition: all 200ms ease ;
+ -moz-transition: all 200ms ease ;
+ -webkit-transition: all 200ms ease ;
+ top: 3px ;
+ width: 20px ;
+}
+
+cb-toggle span.label {
+ display: inline-block ;
+ margin-left: 7px ;
+ text-transform: uppercase ;
+ vertical-align: top ;
+}
+
+cb-toggle.for-on div.switch {
+ background-color: #30659B ;
+}
+
+cb-toggle.for-on span.thumb {
+ left: 100% ;
+ margin-left: -24px ;
+}
+
+form {
+ border: 1px dashed #CCCCCC ;
+ border-radius: 4px 4px 4px 4px ;
+ padding: 20px 20px 20px 20px ;
+}
+
+form p {
+ margin: 0px 0px 0px 0px ;
+}
diff --git a/public/docs/ts/latest/cookbook/_data.json b/public/docs/ts/latest/cookbook/_data.json
index f82d816cef..df29db6bef 100644
--- a/public/docs/ts/latest/cookbook/_data.json
+++ b/public/docs/ts/latest/cookbook/_data.json
@@ -26,6 +26,11 @@
"intro": "Use relative URLs for component templates and styles."
},
+ "custom-form-controls": {
+ "title": "Custom Form Controls",
+ "intro": "Creating custom form controls using ngModel."
+ },
+
"dependency-injection": {
"title": "Dependency Injection",
"intro": "Techniques for Dependency Injection"
diff --git a/public/docs/ts/latest/cookbook/custom-form-controls.jade b/public/docs/ts/latest/cookbook/custom-form-controls.jade
new file mode 100644
index 0000000000..18d7391f00
--- /dev/null
+++ b/public/docs/ts/latest/cookbook/custom-form-controls.jade
@@ -0,0 +1,180 @@
+include ../_util-fns
+
+a(id='top')
+
+:marked
+ When we want to make a custom form control, the first thing that we want to do is **not
+ make a custom form control**. By that, I mean that we want to build our components as if
+ they know nothing about forms. In the same way that the native Input, Select, and
+ Textarea elements know nothing about `ngModel`, neither should our custom components.
+ Binding a component to a form-based interaction is the responsibility of an external
+ "glue and translation layer" know as a "value accessor".
+
+:marked
+ ## Building A Basic Component
+
+:marked
+ To explore the relationship between components and form controls, we'll start by
+ building a Toggle component that displays an On/Off switch. Our component will accept
+ a `[value]` input binding and expose a `(valueChange)` output event:
+
+:marked
+ **See the [live example](/resources/live-examples/cb-custom-form-controls/ts/plnkr.html)**.
+
++makeExample( 'cb-custom-form-controls/ts/app/toggle.component.ts', 'component', 'app/toggle.component.ts - Our Custom Widget' )(format='.')
+
+:marked
+ At this point, we might actually be done.
+
+:marked
+ If all we need to do is implement a proper one-way data flow, this Toggle component is
+ ready for consumption; we can use the `[value]` input to assign a value and the
+ `(valueChange)` output to react to user interactions.
+
+code-example(format='')
+ <cb-toggle [value]='isOn' (valueChange)='handleChange($event)'></cb-toggle>
+
+:marked
+ In fact, even if we want to implement a two-way data flow, this Toggle component is
+ equally viable thanks to the `[x]` and `(xChange)` naming convention that we followed:
+
+code-example(format='')
+ <cb-toggle [(value)]='isOn'></cb-toggle>
+
+:marked
+ ## Connecting Components To The Form
+
+:marked
+ For most situations, the preceding code is likely to be sufficient. However, if we
+ need to take advantage of the robust form management features that Angular provides,
+ we need to create a "value accessor" that facilitates bi-directional communication
+ between the parent form and our component. The value accessor has several
+ responsibilities:
+
+:marked
+ * Push external data into the component.
+ * Announce data-change events emitted by the component.
+ * Loop data-change events back into the component (*implementing two-way data flow*).
+ * Format data on the way into the component (*implementing optional formatters*).
+ * Parse data on the way out of the component (*implementing optional parsers*).
+ * Coordinate with change-detection strategies.
+ * Coordinate with the component life-cycle event handlers.
+
+:marked
+ The value accessor must implement the `ControlValueAccessor` interface:
+
+:marked
+ * `writeValue(obj: any) : void`
+ * `registerOnChange(fn: any) : void`
+ * `registerOnTouched(fn: any) : void`
+
+:marked
+ In order for `ngModel` to be able to consume the value accessor, the value accessor
+ needs to be made available within the dependency injector using the `NG_VALUE_ACCESSOR`
+ token. Typically, this is done using an attribute directive that selects on the target
+ element's node-name but limits the match based on the existence of an attribute that
+ implies `ngModel` usage. For example, the default value accessor for `Textarea` uses
+ the following selector:
+
+:marked
+ `textarea[ngControl],textarea[ngFormControl],textarea[ngModel]`
+
+:marked
+ In this scenario, the attribute directive is typically playing two roles. On the one
+ hand, it is a directive that is providing the value accessor service to the injector
+ associated with the target component. But, on the other hand, it is also implementing
+ the value accessor interface. In other words, it is both a directive and a value
+ accessor. In order to accomplish this, we have to define a provider that points back
+ to itself:
+
++makeExample( 'cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts', 'provider', 'app/toggle-ng-model.directive.ts - Provider Meta-Data' )(format='.')
+
+:marked
+ Notice that our `ToggleNgModelDirective` directive meta-data defines a `useExisting`
+ provider that is nothing more than a `forwardRef()` back to itself. And, that our
+ directive implements the `ControlValueAccessor` interface. At this point, when the
+ `ngModel` directive requests the `NG_VALUE_ACCESSOR` injectable, Angular will provide
+ it with the `ToggleNgModelDirective` class instance.
+
+:marked
+ Now, implementing the actual `ControlValueAccessor` interface is complicated. But, to
+ some degree, it can be boiled down to a single method that applies the value change
+ to the target component (which, in our case, is the `ToggleComponent` component):
+
++makeExample( 'cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts', 'applychanges', 'app/toggle-ng-model.directive.ts - Applying Value Changes' )(format='.')
+
+:marked
+ At this point, the rest of the value accessor is little more than a set of methods that
+ marshal external requests (from either the `ToggleComponent` component or the `ngModel`
+ directive) and invoke the above method with the appropriate arguments. Bringing it all
+ together, our Directive / Value Accessor implementation looks like this:
+
++makeExample( 'cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts', 'valueaccesor', 'app/toggle-ng-model.directive.ts - Bringing It All Together' )(format='.')
+
+:marked
+ In this case, we didn't implement any optional parsers or formatters. But, if we wanted
+ to, we could parse the data in our `handleValueChange()` method; and, we could format
+ the data in our `writeValue()` method.
+
+:marked
+ Now that we have our value accessor defined, we can use our our `ToggleComponent`
+ component with or without `ngModel`. Of course, if we want to use `ngModel`, we have to
+ provide the calling context with the both the `ToggleComponent` and the
+ `ToggleNgModelDirective` directives; otherwise, `ngModel` won't know how to access the
+ underlying data. In the following view, notice that we are using four separate
+ instances of the `ToggleComponent` component, each with a slightly different syntax and
+ access pattern.
+
++makeExample( 'cb-custom-form-controls/ts/app/app.component.ts', 'providers', 'app/app.component.ts - Consuming ToggleComponent With ngModel' )(format='.')
+
+:marked
+ The last Toggle in the example is wrapped in a `form` element that exposes view-local
+ references for `#toggleForm` and `#toggle`. With these references we can confirm that
+ the appropriate CSS classes - `ng-pristine` and `ng-dirty` - are present on the last
+ toggle when we interact with it.
+
+figure.image-display
+ img( src='/resources/images/cookbooks/custom-form-controls/custom-form-controls.gif' alt='Custom form controls with ngModel' )
+
+:marked
+ Here's the complete solution:
+
++makeTabs(
+ `
+ cb-custom-form-controls/ts/app/main.ts,
+ cb-custom-form-controls/ts/app/app.component.ts,
+ cb-custom-form-controls/ts/app/toggle.component.ts,
+ cb-custom-form-controls/ts/app/toggle-ng-model.directive.ts,
+ cb-custom-form-controls/ts/sample.css
+ `,
+ '',
+ `
+ main.ts,
+ app.component.ts,
+ toggle.component.ts,
+ toggle-ng-model.directive.ts,
+ sample.css
+ `
+)
+
+.l-sub-section
+ :marked
+ ### Future Improvements
+
+ :marked
+ If we study the above code, we can see that the value accessor functionality depends
+ on a few references:
+
+ :marked
+ * The target component.
+ * The name of the property being mutated.
+ * The `changeDetectorRef`.
+
+ :marked
+ Since the bulk of the logic is already encapsulated within the
+ `applyChangeToTarget()` method, it would be rather easy to move all of this logic
+ into an abstract base class. Then, our concrete class would do little more than
+ invoke the super class constructor with the appropriate references.
+
+:marked
+ [Back to top](#top)
diff --git a/public/resources/images/cookbooks/custom-form-controls/custom-form-controls.gif b/public/resources/images/cookbooks/custom-form-controls/custom-form-controls.gif
new file mode 100644
index 0000000000..e6d7ea427e
Binary files /dev/null and b/public/resources/images/cookbooks/custom-form-controls/custom-form-controls.gif differ