1

These bindings work great:

<input type="checkbox" (click)="foo.bar()" [(ngModel)]="foo.baz">

But how do I delegate those bindings to an input inside a component?

<custom-checkbox>Check me!</custom-checkbox>

...and here's the "custom-checkbox.component.html":

<span class="checkbox-wrap">
  <input type="checkbox" class="checkbox"> <!-- This should have all the bindings -->
  <span class="checkbox-styling"></span>
  <span class="checkbox-label"><ng-content></ng-content></span>
</span>

I've been using Angular 1 for a couple years, but just started using Angular 2 this week. I've read many articles such as Custom form controls in Angular, Angular docs, other somewhat-similar Stack Overflow questions, and the TypeScript used on Angular 2's Material Checkbox but I still don't get how they pulled this off. It's seems like it should be more straight forward.

This is one of many custom UI elements I need to make, so I'm hoping this example will help me understand the principles and implementation I'm missing.

I know I could use a input checkbox with a selector on it, and wrap it, but I want to be as clean as the Angular 2 Material checkbox is.

I'm essentially doing the same thing they are (Angular 2 Material), but with our own styling, and much more simplistic than all the options they provide. Just like they said,

<md-checkbox> provides the same functionality as a native <input type="checkbox"> enhanced with Material Design styling and animations.

and that's what I'm trying to do.

2
  • put bindings directly on component Commented Feb 16, 2017 at 22:23
  • @RomanC If I do that, the model won't apply to the checkbox's truthy state, and I also get this error in the console: EXCEPTION: Error in ./AppComponent class AppComponent - inline template:4:41 caused by: No value accessor for form control with unspecified name attribute Commented Feb 16, 2017 at 22:32

1 Answer 1

1

Though Angular 1's link function seemed to make this whole process easier, this article seems to give me the guts that Angular 2 Material is using through built-in interfaces with a good example: Angular 2: Connect your custom control to ngModel with Control Value Accessor.

This allows the development teams to use my components by binding directly to a component but relaying the bindings to the checkbox (or other form element) inside my template.

Quoting from the site for ease, and for documentation persistence if the link breaks:

So without further ado, here is our component:

import { Component, forwardRef } from '@angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor } from '@angular/forms';

const noop = () => {
};

export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
    provide: NG_VALUE_ACCESSOR,
    useExisting: forwardRef(() => CustomInputComponent),
    multi: true
};

@Component({
    selector: 'custom-input',
    template: `<div class="form-group">
                    <label><ng-content></ng-content>
                        <input [(ngModel)]="value"
                                class="form-control"
                                (blur)="onBlur()" >
                    </label>
                </div>`,
    providers: [CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]
})
export class CustomInputComponent implements ControlValueAccessor {

    //The internal data model
    private innerValue: any = '';

    //Placeholders for the callbacks which are later providesd
    //by the Control Value Accessor
    private onTouchedCallback: () => void = noop;
    private onChangeCallback: (_: any) => void = noop;

    //get accessor
    get value(): any {
        return this.innerValue;
    };

    //set accessor including call the onchange callback
    set value(v: any) {
        if (v !== this.innerValue) {
            this.innerValue = v;
            this.onChangeCallback(v);
        }
    }

    //Set touched on blur
    onBlur() {
        this.onTouchedCallback();
    }

    //From ControlValueAccessor interface
    writeValue(value: any) {
        if (value !== this.innerValue) {
            this.innerValue = value;
        }
    }

    //From ControlValueAccessor interface
    registerOnChange(fn: any) {
        this.onChangeCallback = fn;
    }

    //From ControlValueAccessor interface
    registerOnTouched(fn: any) {
        this.onTouchedCallback = fn;
    }

}

We are then able to use this custom control as follows:

<form>

    <custom-input name="someValue"
                  [(ngModel)]="dataModel">
          Enter data:
    </custom-input>

</form>

Thanks Almero Steyn!!

Sign up to request clarification or add additional context in comments.

2 Comments

Would it be possible to use a formControl instead of ngModel? I am asking as you could use formControl.valueChanges as subscription and trigger onChangeCallback.
@Gambo Unfortunately I have to support ngModels on everything. But thanks for looking at an alternative.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.