I have a custom element :
<div formControlName="surveyType">
<div *ngFor="let type of surveyTypes"
(click)="onSelectType(type)"
[class.selected]="type === selectedType">
<md-icon>{{ type.icon }}</md-icon>
<span>{{ type.description }}</span>
</div>
</div>
When I try to add the formControlName, I get an error message:
ERROR Error: No value accessor for form control with name:
'surveyType'
I tried to add ngDefaultControl without success.
It seems it's because there is no input/select... and I dont know what to do.
I would like to bind my click to this formControl in order that when someone clicks on the entire card that would push my 'type' into the formControl. Is it possible?
You can use formControlName only on directives which implement ControlValueAccessor.
Implement the interface
So, in order to do what you want, you have to create a component which implements ControlValueAccessor, which means implementing the following three functions:
writeValue (tells Angular how to write value from model into view)
registerOnChange (registers a handler function that is called when the view changes)
registerOnTouched (registers a handler to be called when the component receives a touch event, useful for knowing if the component has been focused).
Register a provider
Then, you have to tell Angular that this directive is a ControlValueAccessor (interface is not gonna cut it since it is stripped from the code when TypeScript is compiled to JavaScript). You do this by registering a provider.
The provider should provide NG_VALUE_ACCESSOR and use an existing value. You'll also need a forwardRef here. Note that NG_VALUE_ACCESSOR should be a multi provider.
For example, if your custom directive is named MyControlComponent, you should add something along the following lines inside the object passed to #Component decorator:
providers: [
{
provide: NG_VALUE_ACCESSOR,
multi: true,
useExisting: forwardRef(() => MyControlComponent),
}
]
Usage
Your component is ready to be used. With template-driven forms, ngModel binding will now work properly.
With reactive forms, you can now properly use formControlName and the form control will behave as expected.
Resources
Custom Form Controls in Angular by Thoughtram
Angular Custom Form Controls with Reactive Forms and NgModel by Cory Rylan
You should use formControlName="surveyType" on an input and not on a div
The error means, that Angular doesn't know what to do when you put a formControl on a div.
To fix this, you have two options.
You put the formControlName on an element, that is supported by Angular out of the box. Those are: input, textarea and select.
You implement the ControlValueAccessor interface. By doing so, you're telling Angular "how to access the value of your control" (hence the name). Or in simple terms: What to do, when you put a formControlName on an element, that doesn't naturally have a value associated with it.
Now, implementing the ControlValueAccessor interface can be a bit daunting at first. Especially because there isn't much good documentation of this out there and you need to add a lot of boilerplate to your code. So let me try to break this down in some simple-to-follow steps.
Move your form control into its own component
In order to implement the ControlValueAccessor, you need to create a new component (or directive). Move the code related to your form control there. Like this it will also be easily reusable. Having a control already inside a component might be the reason in the first place, why you need to implement the ControlValueAccessor interface, because otherwise you will not be able to use your custom component together with Angular forms.
Add the boilerplate to your code
Implementing the ControlValueAccessor interface is quite verbose, here's the boilerplate that comes with it:
import {Component, OnInit, forwardRef} from '#angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '#angular/forms';
#Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// a) copy paste this providers property (adjust the component name in the forward ref)
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// b) Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// c) copy paste this code
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// d) copy paste this code
writeValue(input: string) {
// TODO
}
So what are the individual parts doing?
a) Lets Angular know during runtime that you implemented the ControlValueAccessor interface
b) Makes sure you're implementing the ControlValueAccessor interface
c) This is probably the most confusing part. Basically what you're doing is, you give Angular the means to override your class properties/methods onChange and onTouch with it's own implementation during runtime, such that you can then call those functions. So this point is important to understand: You don't need to implement onChange and onTouch yourself (other than the initial empty implementation). The only thing your doing with (c) is to let Angular attach it's own functions to your class. Why? So you can then call the onChange and onTouch methods provided by Angular at the appropriate time. We'll see how this works down below.
d) We'll also see how the writeValue method works in the next section, when we implement it. I've put it here, so all required properties on ControlValueAccessor are implemented and your code still compiles.
Implement writeValue
What writeValue does, is to do something inside your custom component, when the form control is changed on the outside. So for example, if you have named your custom form control component app-custom-input and you'd be using it in the parent component like this:
<form [formGroup]="form">
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
then writeValue gets triggered whenever the parent component somehow changes the value of myFormControl. This could be for example during the initialization of the form (this.form = this.formBuilder.group({myFormControl: ""});) or on a form reset this.form.reset();.
What you'll typically want to do if the value of the form control changes on the outside, is to write it to a local variable which represents the form control value. For example, if your CustomInputComponent revolves around a text based form control, it could look like this:
writeValue(input: string) {
this.input = input;
}
and in the html of CustomInputComponent:
<input type="text"
[ngModel]="input">
You could also write it directly to the input element as described in the Angular docs.
Now you have handled what happens inside of your component when something changes outside. Now let's look at the other direction. How do you inform the outside world when something changes inside of your component?
Calling onChange
The next step is to inform the parent component about changes inside of your CustomInputComponent. This is where the onChange and onTouch functions from (c) from above come into play. By calling those functions you can inform the outside about changes inside your component. In order to propagate changes of the value to the outside, you need to call onChange with the new value as the argument. For example, if the user types something in the input field in your custom component, you call onChange with the updated value:
<input type="text"
[ngModel]="input"
(ngModelChange)="onChange($event)">
If you check the implementation (c) from above again, you'll see what's happening: Angular bound it's own implementation to the onChange class property. That implementation expects one argument, which is the updated control value. What you're doing now is you're calling that method and thus letting Angular know about the change. Angular will now go ahead and change the form value on the outside. This is the key part in all this. You told Angular when it should update the form control and with what value by calling onChange. You've given it the means to "access the control value".
By the way: The name onChange is chosen by me. You could choose anything here, for example propagateChange or similar. However you name it though, it will be the same function that takes one argument, that is provided by Angular and that is bound to your class by the registerOnChange method during runtime.
Calling onTouch
Since form controls can be "touched", you should also give Angular the means to understand when your custom form control is touched. You can do it, you guessed it, by calling the onTouch function. So for our example here, if you want to stay compliant with how Angular is doing it for the out-of-the-box form controls, you should call onTouch when the input field is blurred:
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
Again, onTouch is a name chosen by me, but what it's actual function is provided by Angular and it takes zero arguments. Which makes sense, since you're just letting Angular know, that the form control has been touched.
Putting it all together
So how does that look when it comes all together? It should look like this:
// custom-input.component.ts
import {Component, OnInit, forwardRef} from '#angular/core';
import {ControlValueAccessor, FormControl, NG_VALUE_ACCESSOR} from '#angular/forms';
#Component({
selector: 'app-custom-input',
templateUrl: './custom-input.component.html',
styleUrls: ['./custom-input.component.scss'],
// Step 1: copy paste this providers property
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CustomInputComponent),
multi: true
}
]
})
// Step 2: Add "implements ControlValueAccessor"
export class CustomInputComponent implements ControlValueAccessor {
// Step 3: Copy paste this stuff here
onChange: any = () => {}
onTouch: any = () => {}
registerOnChange(fn: any): void {
this.onChange = fn;
}
registerOnTouched(fn: any): void {
this.onTouch = fn;
}
// Step 4: Define what should happen in this component, if something changes outside
input: string;
writeValue(input: string) {
this.input = input;
}
// Step 5: Handle what should happen on the outside, if something changes on the inside
// in this simple case, we've handled all of that in the .html
// a) we've bound to the local variable with ngModel
// b) we emit to the ouside by calling onChange on ngModelChange
}
// custom-input.component.html
<input type="text"
[(ngModel)]="input"
(ngModelChange)="onChange($event)"
(blur)="onTouch()">
// parent.component.html
<app-custom-input [formControl]="inputTwo"></app-custom-input>
// OR
<form [formGroup]="form" >
<app-custom-input formControlName="myFormControl"></app-custom-input>
</form>
More Examples
Example with Input: https://stackblitz.com/edit/angular-control-value-accessor-simple-example-tsmean
Example with Lazy Loaded Input: https://stackblitz.com/edit/angular-control-value-accessor-lazy-input-example-tsmean
Example with Button: https://stackblitz.com/edit/angular-control-value-accessor-button-example-tsmean
Nested Forms
Note that Control Value Accessors are NOT the right tool for nested form groups. For nested form groups you can simply use an #Input() subform instead. Control Value Accessors are meant to wrap controls, not groups! See this example how to use an input for a nested form: https://stackblitz.com/edit/angular-nested-forms-input-2
Sources
https://angular.io/api/forms/ControlValueAccessor
https://www.tsmean.com/articles/angular/angular-control-value-accessor-example/
For me it was due to "multiple" attribute on select input control as Angular has different ValueAccessor for this type of control.
const countryControl = new FormControl();
And inside template use like this
<select multiple name="countries" [formControl]="countryControl">
<option *ngFor="let country of countries" [ngValue]="country">
{{ country.name }}
</option>
</select>
More details ref Official Docs
Related
I have a constants file constants.ts:
export const C0NST = "constant";
I access it in a service some.service.ts like so:
import { C0NST } from './constants';
console.log(C0NST); // "constant"
However, when I access it in a component template:
some.component.ts:
import { C0NST } from './constants';
some.component.html:
{{ C0NST }} <!-- Outputs nothing -->
However defining a member in the component class works:
some.component.ts
public const constant = C0NST;
some.component.html
{{ constant }} <!-- constant -->
I don't understand why I was able to access the imported constant directly in the service class but not in the component template even though I imported it in the component class.
In Angular2, the template can only access fields and methods of the component class. Everything else is off-limits. This includes things which are visible to the component class.
The way to go around this is to have a field inside the component, which just references the constant, and use that instead.
It's one limitation of the design, but perhaps you should think a bit more about why you need a constant in the template in the first place. Usually these things are used by components themselves, or services, but not the template.
Since in the Component's template you can only use attributes of the Component's class, you can't directly use any external constants (or external variables).
The most elegant way that I've found so far is the following:
import { MY_CONSTANT } from '../constants';
#Component({
// ...
})
export class MyTestComponent implements OnInit {
readonly MY_CONSTANT = MY_CONSTANT;
// ...
}
which basically just creates a new attribute MY_CONSTANT inside the component class. Using readonly we make sure that the new attribute cannot be modified.
Doing so, in your template you can now use:
{{ MY_CONSTANT }}
The scope of Angular2 template bindings is the component instance. Only what's accessible there can be used in bindings.
You can make it available like
class MyComponent {
myConst = CONST;
}
{{myConst}}
There are two best directions in my opinion:
Wrapping constants as internal component property
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
readonly stateEnum : typeof stateEnum = stateEnum ;
}
Example uses enum, but this can be any type of defined constant. typeof operator gives you all of benefits of TypeScript typing features. You can use then this variable directly in templates:
component.html
<p>{{stateEnum.DOING}}<p>
This solution is less efficient in memory usage context, because you are basically duplicating data (or references to constants) in each component you wish to use it. Beside that, syntax
readonly constData: typeof constData = constData
in my opinion introduce a lot of syntax noise and may be confusing to newcommers
Wrapping external constant in component function
Second option is to wrap your external variable/constant with component function and use that function on template:
enum.ts
export enum stateEnum {
'DOING' = 0,
'DONE',
'FAILED'
}
component.ts
import { stateEnum } from './enum'
export class EnumUserClass {
getEnumString(idx) {
return stateEnum[stateEnum[idx]];
}
}
component.html
<p>{{getEnumString(1)}}</p>
Good thing is that data is not duplicated in controller but other major downside occur. According to Angular team, usage of functions in templates is not recommended due to change detection mechanism, which works way less efficient in case of functions returning values to templates: change detection have no idea does value return by a function has changed, so it will be called way often than needed (and assuming you returning const from it, it's actually needed only once, when populating template view. It may be just a bit efficiency killing to your application (if you are lucky) or it may totally break it down if function resolves with Observable for instance, and you use async pipe to subscribe to results. You can refer to my short article on that HERE
You can create a BaseComponent , it is a place where you should create your constant instances and then you can create your FooComponent extends BaseComponent and you can use your constants.
i have an input element in a component like so :
<input #inputDate
(blur)="realDate(inputDate.value); inputDate.value='' ">
and then i want to use (show) the value of the inputDate on another component, how can i do it?
im new to angular, any help will be appreciated.
If you want to send it to a parent component, bind it to a ngModel and use event emitter to send this value.
If you want to send it to child component, use #Input decorator. (parent-to-child)
However, as you want to send the value from html, you can use jquery or document.querySelector to get the value
This might help you
If you want to send your component to a parent component, you can use the #Output() decorator and an Event Emitter together to send the value to a parent component by doing something like:
#Output()
public input: EventEmitter<string> = new EventEmitter<string>();
<my-input (input)="onInputChange()"></my-input>
// Method to detect for changes on #myInput
public onMyInputChanges(): void {
// ...
input.emit(<#myInputValue>);
}
I'll leave it as an exercise for you to figure out how to detect changes to your input element above.
If you want to send your value to a child component, you can use the #Input() decorator and pass it along as such:
<input #myInput [(ngModel)]="inputValue">
public inputValue: string = '';
<child-component [value]="inputValue"></child-component>
Because you want to use the value directly from the HTML, consider using the #ViewChild decorator which would allow you to use your local reference #inputDate to get the value, like so:
#ViewChild('inputDate')
public myInput: HTMLInputElement;
And then you can access your value through myInput.value and either use it on your component or pass it around with the above methods.
You can also look into using Services which might help you reuse some of this logic across several components if that's your use case (e.g. emitting the value from a Subject and having multiple components listen to changes on your input).
I am doing a deep dive into how two-way databinding works. I am currently puzzled by how updates from the view (say, an input element) propagate to NgControl internally.
In the definition of ControlValueAccessor it mentions that registerOnChange is responsible for view -> model updates (docs where they say it, and src). With a simple directive that we may put on the same input element as [(NgModel)], e.g. <input [(NgModel)]=stuff myInspectorDirective>, I tried playing around with this.
constructor(private ngControl: NgControl) { }
ngOnInit(): void {
// this.ngControl.valueAccessor['onChange'] = () => {};
// uncommenting the above line prevents updates from view to model
}
Uncommenting/commenting the indicated line allows us to allow/block updates from the input element to the model. But I'm puzzled by this because in the source code of DefaultValueAccessor, the one used in this example, onChange is not really doing anything: (_:any) => {}.
So, I would expect that under the hood, e.g. in ng_model.ts or in one of the related classes, like NgControl or FormControl, something happens with the onChange function from the ValueAccessor; setting it or wrapping it in another function, maybe a proxy, or whatever. I did not find anything. Then I went on looking for some code where listeners (for the input event, more explicitly) are explicitly bound to the input element, but no luck either.
I noticed that the OnChanges function calls _setValue, but I'm not sure if I'm going in the right direction when diving into the internals of change detection, as I would expect the listening to changes in the DOM to be related to ControlValueAccessors and/or FormControl/AbstractControl
Anyone feels like elaborating on how this works? :-)
The ControlValueAccessor.registerOnChange is provided by the NgForm.
1) NgModel is registered in NgForm (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/ng_model.ts)
in NgModel.ngOnChanges: this._setUpControl calls this.formDirective.addControl
2) NgForm calls shared setUpControl function (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/ng_form.ts)
import { setUpControl } from './shared';
NgForm.addControl calls setUpControl
3) setUpControl registers change event handler (see https://github.com/angular/angular/blob/master/packages/forms/src/directives/shared.ts)
setUpControl calls setUpViewChangePipeline
function setUpViewChangePipeline(control: FormControl, dir: NgControl): void {
dir.valueAccessor !.registerOnChange((newValue: any) => {
control._pendingValue = newValue;
control._pendingChange = true;
control._pendingDirty = true;
if (control.updateOn === 'change') updateControl(control, dir);
});
}
I imagine I may be missing something really obvious, however this is my situation - I have some data that is being assigned to the ngModel input of a component, e.g:
Typescript:
SomeData = {
SomeValue: 'bar'
}
Fragment of view template:
<foo [(ngModel)]="SomeData.SomeValue"></foo>
Component:
import { Component, OnChanges, SimpleChanges } from '#angular/core';
#Component({
selector: 'foo',
template: `<input type="text" [ngModel]="value" (ngModelChange)="modelChange($event)
(change)="elementChange($event)"/>`
})
export class FooComponent {
ngOnChanges(changes: SimpleChanges) {
// Fired when #Input members change
}
modelChange(value) {
// Fired when a change in the HTML element will change the model, *not* when the model changes from elsewhere
}
elementChange(event) {
// Fired when the HTML element value changes
}
}
As per my comments in the example, I'm able to tell when Inputs change, when the value of the HTML element will change the model, and when the value of the HTML element changes.
I want to be able to know from within the component, when the property that is assigned to ngModel in the view template (i.e. SomeData.SomeValue) changes. I know that Angular does this itself, because it updates the value in the HTML, however I'm at a loss as to how to intercept this change as well, from within the component, so some other action may be taken.
SomeData.SomeValue is not controlled by angular, all you do is tell angular to bind to a property and bind to an event. Angular will then run it's own change detection mechanism which will update the view. If you are interested in how Angular does this take a look at this blog.
If you want to be notified of changes to SomeData.SomeValue you'll have to set up your own system, this can be as simple as a callback or a pub/sub. But it's really too broad to go into here.
I thought I was pretty clear on how Angular Change detection works after this discussion: Why is change detection not happening here when [value] changed?
But take a look at this plunk: https://plnkr.co/edit/jb2k7U3TfV7qX2x1fV4X?p=preview
#Component({
selector: 'simple',
template: `
<div (click)="onClick()">
{{myData[0].name}}
</div>
`,
changeDetection: ChangeDetectionStrategy.OnPush
})
export class Simple {
public #Input() myData;
constructor() {
}
public onClick() {
}
}
Click on a, it's changed to c
I understand that the click event triggers change detection on the App level, but [myData]="testData" is still referring to the same object, and I am using On Push on Simple, why does a get changed?
That's by design.
If you have component with OnPush change detection then its detectChangesInternal function won't be triggered unless one of four things happens:
1) one of its #Inputs changes
~2.4.x
~4.x.x
Note: #Inputs should be presented in template. See issue https://github.com/angular/angular/issues/20611 and comment
2) a bound event is triggered from the component (that is your case)
Caveats: There is some difference here between 2.x.x and 4
Angular ChangeDetectionStrategy.OnPush with child component emitting an event
~2.4.x
~4.x.x
3) you manually mark the component to be checked (ChangeDetectorRef.markForCheck())
4) async pipe calls ChangeDetectorRef.markForCheck() internally
private _updateLatestValue(async: any, value: Object): void {
if (async === this._obj) {
this._latestValue = value;
this._ref.markForCheck();
}
}
https://github.com/angular/angular/blob/2.4.8/modules/%40angular/common/src/pipes/async_pipe.ts#L137
In other words if you set OnPush for component then after the first checking component's status will be changed from CheckOnce to Checked and after that it's waiting as long as we do not change status. It will happen in one of three things above.
See also:
https://github.com/angular/angular/issues/11678#issuecomment-247894782
There are also good explanations of how angular2 change detection work:
https://blog.thoughtram.io/angular/2016/02/22/angular-2-change-detection-explained.html
https://hackernoon.com/everything-you-need-to-know-about-change-detection-in-angular-8006c51d206f
Here is Live Example(Thanks to Paskal) that explains onPush change detection. (Comp16 looks like your component. You can click at this box).