I have a massive headache trying to figure this out. I am wanting to create a custom input element I can use in my Angular/Ionic application. How can I use Angular attributes with these custom elements, like FormControlName for example.
<my-custom-stencil-input type="text" formControlName="firstName"></my-custom-stencil-input>
I have the following code below, but it is showing errors when I attempt to use FormControlName.
import { Component, h, Prop } from '@stencil/core';
@Component({
tag: 'ba-text-input-one',
styleUrl: './text-input-1.component.css',
scoped: true,
})
export class TextInputOne {
@Prop({reflect: true}) type: string;
@Prop({reflect: true}) placeholder: string;
@Prop({reflect: true}) formControlName: string;
render() {
return (
<div id="cmp">
<div id="icon-area">
<slot name="icon"></slot>
</div>
<input id="input-field" formControlName={this.formControlName} type={this.type} />
</div>
);
}
}
The documentation on Stencil is not very thorough on how to approach this.
Essentially, I am wanting to use my own custom Stencil input element in an Angular reactive form.
I had the same problem, but I found a solution:
First, you don't have to create a formControlName in stencil project.
import { Component, Event, EventEmitter, h, Prop } from "@stencil/core";
@Component({
tag: 'my-custom-stencil-input',
styleUrl: 'input.css',
shadow: true
})
export class Input {
@Prop({reflect: true, mutable: true}) label: string;
@Prop({reflect: true, mutable: true}) value: string;
@Prop() type = 'text';
@Event() valueChanged: EventEmitter<string>;
private onInputChangeValue(event: Event) {
this.value = (event.target as HTMLInputElement).value;
this.valueChanged.emit(this.value);
}
render() {
return (
<div>
<label> {this.label} </label>
<input type= {this.type} value= {this.value} onInput= {this.onInputChangeValue.bind(this)}/>
</div>
)
}
}
Then, in the Angular project you have to create a directive because you need to implement the ControlValueAccessor that acts as a bridge between the Angular forms API and a native element in the DOM.
import { Directive, forwardRef, HostBinding, HostListener } from '@angular/core';
import { ControlValueAccessor, NG_VALUE_ACCESSOR } from '@angular/forms';
@Directive({
selector: '[appAccessor]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => FormDirective),
multi: true,
},
],
})
export class FormDirective implements ControlValueAccessor {
@HostBinding('value') hostValue: any;
lastValue: any;
private onChange = (value: any) => {};
private onTouched = () => {};
writeValue(value: any) {
this.hostValue = this.lastValue = value == null ? '' : value;
}
registerOnChange(fn: (value: any) => void) {
this.onChange = fn;
}
registerOnTouched(fn: () => void) {
this.onTouched = fn;
}
@HostListener('valueChanged', ['$event.detail'])
_handleInputEvent(value: any) {
if (JSON.stringify(value) !== JSON.stringify(this.lastValue)) {
this.lastValue = value;
this.onChange(value);
this.onTouched();
}
}
}
Finally, use the custom element (in this case a input) in reactive forms:
<form [formGroup]="myFormGroup">
<my-custom-stencil-input label="Any label" appAccessor formControlName="control1" ></my-custom-stencil-input>
</form>
And that's it.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With