From 434575d754c32af4071e29c16e6d74d45be30f80 Mon Sep 17 00:00:00 2001 From: rellis-of-rhindleton Date: Thu, 8 Feb 2018 21:34:07 -0500 Subject: [PATCH 1/3] Creating a custom form field docs -- updated ngControl section to reflect errors injecting NgControl If the component already implements ControlValueAccessor, injecting NgControl in the constructor (as described in the docs) may fail. Added an alternate way. --- .../creating-a-custom-form-field-control.md | 39 ++++++++++++++++--- 1 file changed, 33 insertions(+), 6 deletions(-) diff --git a/guides/creating-a-custom-form-field-control.md b/guides/creating-a-custom-form-field-control.md index 941ebea956d6..2624237be793 100644 --- a/guides/creating-a-custom-form-field-control.md +++ b/guides/creating-a-custom-form-field-control.md @@ -159,21 +159,48 @@ private _placeholder: string; This property allows the form field control to specify the `@angular/forms` control that is bound to this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just -set this to `null` in our component. In any real component, you would probably want to implement -`ControlValueAccessor` so that your component can work with `formControl` and `ngModel`. +set this to `null` in our component. ```ts -ngControl = null; +ngControl: NgControl = null; ``` -If you did implement `ControlValueAccessor`, you could simply inject the `NgControl` and make it -publicly available. (For additional information about `ControlValueAccessor` see the -[API docs](https://angular.io/api/forms/ControlValueAccessor).) +In any real component, you would probably want to implement `ControlValueAccessor` so that your component +can work with `formControl` and `ngModel`. If you do implement `ControlValueAccessor`, you will need to get +a reference to the `NgControl` associated with yoru control, and make it publicly available. + +One way to do this is to inject it: ```ts constructor(..., @Optional() @Self() public ngControl: NgControl) { ... } ``` +...however, if your component implements `ControlValueAccessor`, it may already provide `NG_VALUE_ACCESSOR` and attempting to inject `NgControl` this may cause a dependency injection error ("Cannot instantiate cyclic dependency"). + +If necessary, inject the `Injector` itself and use it to get the `NgControl` directly: + +```ts + +ngControl: NgControl; + +constructor( + private readonly _injector: Injector, + // ... +) { } + +// ... + +ngAfterViewInit() { + this.ngControl = this._injector.get(NgControl); +} + +``` + +Note that this approach, called *service location*, is considered an anti-pattern except in cases where specifically necessary (as it may be here). + +For additional information about `ControlValueAccessor` see the [API docs](https://angular.io/api/forms/ControlValueAccessor). + + #### `focused` This property indicates whether or not the form field control should be considered to be in a From 5fe53336ffaa76dda72a50a9d843059eab5f7796 Mon Sep 17 00:00:00 2001 From: rellis-of-rhindleton Date: Fri, 9 Feb 2018 09:56:27 -0500 Subject: [PATCH 2/3] Update creating-a-custom-form-field-control.md --- .../creating-a-custom-form-field-control.md | 41 ++++++++----------- 1 file changed, 17 insertions(+), 24 deletions(-) diff --git a/guides/creating-a-custom-form-field-control.md b/guides/creating-a-custom-form-field-control.md index 2624237be793..54ef7057041f 100644 --- a/guides/creating-a-custom-form-field-control.md +++ b/guides/creating-a-custom-form-field-control.md @@ -157,47 +157,40 @@ private _placeholder: string; #### `ngControl` -This property allows the form field control to specify the `@angular/forms` control that is bound to -this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just -set this to `null` in our component. +This property allows the form field control to specify the `@angular/forms` control that is bound to this component. Since we haven't set up our component to act as a `ControlValueAccessor`, we'll just set this to `null` in our component. ```ts ngControl: NgControl = null; ``` -In any real component, you would probably want to implement `ControlValueAccessor` so that your component -can work with `formControl` and `ngModel`. If you do implement `ControlValueAccessor`, you will need to get -a reference to the `NgControl` associated with yoru control, and make it publicly available. +It is likely you will want to implement `ControlValueAccessor` so that your component can work with `formControl` and `ngModel`. If you do implement `ControlValueAccessor` you will need to get a reference to the `NgControl` associated with your control and make it publicly available. -One way to do this is to inject it: +The easy way is to add it as a public property to your constructor and let dependency injection handle it: ```ts -constructor(..., @Optional() @Self() public ngControl: NgControl) { ... } +constructor( + ..., + @Optional() @Self() public ngControl: NgControl, + ..., +) { } ``` -...however, if your component implements `ControlValueAccessor`, it may already provide `NG_VALUE_ACCESSOR` and attempting to inject `NgControl` this may cause a dependency injection error ("Cannot instantiate cyclic dependency"). +Note that if your component implements `ControlValueAccessor`, it may already be set up to provide `NG_VALUE_ACCESSOR` (in the `providers` part of the component's decorator, or possibly in a module declaration). If so you may get a *cannot instantiate cyclic dependency* error. -If necessary, inject the `Injector` itself and use it to get the `NgControl` directly: +To resolve this, remove the `NG_VALUE_ACCESSOR` and instead set the value accessor directly: ```ts - -ngControl: NgControl; - constructor( - private readonly _injector: Injector, - // ... -) { } - -// ... - -ngAfterViewInit() { - this.ngControl = this._injector.get(NgControl); + ..., + @Optional() @Self() public ngControl: NgControl, + ..., +) { + // Setting the value accessor directly (instead of using + // the providers) to avoid running into a circular import. + if (this.ngControl != null) { this.ngControl.valueAccessor = this; } } - ``` -Note that this approach, called *service location*, is considered an anti-pattern except in cases where specifically necessary (as it may be here). - For additional information about `ControlValueAccessor` see the [API docs](https://angular.io/api/forms/ControlValueAccessor). From cd96cf2c6bbb615c13cb28fd5f1f41ad5d088ff8 Mon Sep 17 00:00:00 2001 From: rellis-of-rhindleton Date: Fri, 9 Feb 2018 12:22:14 -0500 Subject: [PATCH 3/3] added word "provider" for clarity --- guides/creating-a-custom-form-field-control.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/guides/creating-a-custom-form-field-control.md b/guides/creating-a-custom-form-field-control.md index 54ef7057041f..6c6b3ed113d6 100644 --- a/guides/creating-a-custom-form-field-control.md +++ b/guides/creating-a-custom-form-field-control.md @@ -177,7 +177,7 @@ constructor( Note that if your component implements `ControlValueAccessor`, it may already be set up to provide `NG_VALUE_ACCESSOR` (in the `providers` part of the component's decorator, or possibly in a module declaration). If so you may get a *cannot instantiate cyclic dependency* error. -To resolve this, remove the `NG_VALUE_ACCESSOR` and instead set the value accessor directly: +To resolve this, remove the `NG_VALUE_ACCESSOR` provider and instead set the value accessor directly: ```ts constructor(