Reputation: 4942
I'm trying to make a custom counter input component, and I'm struggling to have the input value controlled by the custom increment/decrement buttons.
This is what I'm trying to make:
I want to use content projection and have the input exposed in order to use it in forms and add properties as I would on a regular number input. So a directive on an input seems like the way to go:
<my-counter-input>
<input [(ngModel)]="count" myInputRef />
</my-counter-input>
The component itself would have two buttons and a ng-content
-slot:
<button (click)="decrement()">-</button>
<ng-content select="input"></ng-content>
<button (click)="increment()">+</button>
So far everything seems alright. Now I can interact with the directive by using the @ContentChild
decorator.
@Component({
selector: 'my-counter-input',
templateUrl: './counter-input.component.html',
styleUrls: ['./counter-input.component.scss'],
})
export class CounterInputComponent {
@ContentChild(InputRefDirective)
public input: InputRefDirective;
constructor() {}
public increment() {
this.input.increment();
}
public decrement() {
this.input.decrement();
}
}
I guess this is where the problem arises. I'm binding the value of the input element to the directive, but neither increment/decrement method has any effect on the native input value. I'd like to have a two-way binding on the native input value, but it seems like this is not the case.
@Directive({
selector: 'input [myInputRef]',
})
export class InputRefDirective {
@HostBinding('type') readonly type = 'number';
@HostBinding('value') value = 5;
public increment() {
this.value++;
}
public decrement() {
this.value--;
}
}
I'm not sure how I should go about this. How can I update the value of the native input and also trigger the native change event?
Upvotes: 0
Views: 5865
Reputation: 161
Unless you have to use content projection and let the parent provide the input control/template, you can just create your reusable component as a standalone component that handles everything. It's a little simpler that way. Here is a forked example.
But the other two examples already given do fix the update issue you were facing.
Upvotes: 0
Reputation: 58
You can tell to the ngModel to update on initialization and on each change
public increment() {
this.value++;
this.updateNgModel(this.value);
}
public decrement() {
this.value--;
this.updateNgModel(this.value);
}
private updateNgModel(value: number) {
this.ngModel.update.emit(value);
}
https://stackblitz.com/edit/angular-ivy-mw2ltg?file=src/app/input-ref.directive.ts
Upvotes: 1
Reputation: 27323
Use NgControl
A base class that all FormControl-based directives extend. It binds a FormControl object to a DOM element.
We can Inject the NgControl in directive and the Angular DI framework will provide us the closest form control directive. Then we can set the value to formControl dynamically.
import { Directive, HostBinding, AfterViewInit } from "@angular/core";
import { NgControl } from "@angular/forms";
@Directive({
selector: "[appInput]"
})
export class InputDirective implements AfterViewInit {
@HostBinding("type") readonly type = "number";
value = 5;
ngAfterViewInit(){
setTimeout(()=>{this.control.control.setValue(this.value)})
}
constructor(private control: NgControl) {
}
public increment() {
this.control.control.setValue(this.value++);
}
public decrement() {
this.control.control.setValue(this.value--);
}
}
Upvotes: 3