Angular ControlValueAccessor checkbox always true when using SVG - angular

I have a custom checkbox component that currently uses some Material Icons icons to display. I'm using the component in a few places, but I'm having trouble with one specific instance.
I have a grid with checkboxes along the left side to select multiple rows at once. When <i> is used to display the icon from the font, these checkboxes work properly. When I switch to using an SVG instead, these checkboxes break. I can only select one at a time, and I can't deselect one that's selected. If I check a box, it checks like you'd expect. If I click it again, nothing happens--the box stays checked. If I check a different box, the first one is unchecked.
This is the original checkbox component template:
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<i class="material-icons md-18 indeterminate norowclick">indeterminate_check_box</i>
<i class="material-icons md-18 checked norowclick">check_box</i>
<i class="material-icons md-18 unchecked norowclick">check_box_outline_blank</i>
</label>
This is the new template, using angular-svg-icon. When rendered, there's an <svg-icon> element with the <svg> element as its only child. This is the only change made to any of the code, this is what causes it to break.
<label class="my-checkbox" [class.pointer]="!disabled">
<input
type="checkbox"
class="form-control norowclick"
[class.indeterminateinput]="indeterminate"
[checked]="checked"
[(ngModel)]="value"
(blur)="onBlur()"
[disabled]="disabled"
>
<svg-icon name="indeterminate_check_box" class="icon-18 indeterminate norowclick"></svg-icon>
<svg-icon name="check_box" class="icon-18 checked norowclick"></svg-icon>
<svg-icon name="check_box_outline_blank" class="icon-18 unchecked norowclick"></svg-icon>
</label>
This is the checkbox component code:
#Component({
selector: 'my-checkbox',
templateUrl: './my-checkbox.component.html',
styleUrls: ['./my-checkbox.component.less'],
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => MyCheckboxComponent),
multi: true
}
]
})
export class MyCheckboxComponent implements OnInit, ControlValueAccessor {
#Input() formControlName: string;
#Input() checked: boolean = false;
#Input() indeterminate: boolean = false;
#Input() disabled: boolean = false;
public control: AbstractControl;
private innerValue: any = '';
private controlContainer: ControlContainer;
private onTouchedCallback: () => void = noop;
private onChangeCallback: (_: any) => void = noop;
constructor(
#Optional()
#Host()
#SkipSelf()
private _controlContainer: ControlContainer
) {
this.controlContainer = _controlContainer;
}
ngOnInit() {
this._getFormControl();
}
private _getFormControl() {
if (this.controlContainer) {
if (this.formControlName) {
this.control = this.controlContainer.control.get(this.formControlName);
}
}
}
get value(): any {
return this.innerValue;
}
set value(v: any) {
if (v !== this.innerValue) {
this.indeterminate = false;
this.innerValue = v;
this.onChangeCallback(v);
}
}
onBlur() {
this.onTouchedCallback();
}
writeValue(value: any) {
if (value !== this.innerValue) {
this.innerValue = value;
}
}
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
}
The grid column is rendered as a template like this. When passed to the checkSecret() function, $event.target.checked is always true in the new version.
<ng-template #checkBoxTemplate let-value="value" let-item="item">
<div class="rowCheck">
<span *ngIf="!item['isFolder']" [class.rowHoverInline]="!hasSelections()">
<my-checkbox class="norowclick" [checked]="item['isSelected']" (change)="checkSecret(item, $event)"></my-checkbox>
</span>
</div>
</ng-template>

Related

*ngIf block form validation

Hello I have been blocked for several days on this problem
I explain to you I have an input or I apply a message according to some condition however I have another condition that says if it is a multiline text used a textarea if it is an inline use an input
When I set my condition *ngIf = "! multiline" in my input I have a problem I have the error message:
Can not read property 'errors' of undefinied
I have applied something, I have encapsulate in a div my input including my condition and I thus, add myModel && myModel.error so that the object is initialized
nothing works
if anyone has a solution I'm interested
this is my code :
import {
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
ElementRef,
forwardRef,
Input,
OnInit,
ViewChild
} from '#angular/core'
import {
ControlValueAccessor,
NG_VALUE_ACCESSOR
} from '#angular/forms'
#Component({
selector: 'mae-input',
templateUrl: './input.html',
changeDetection: ChangeDetectionStrategy.OnPush,
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => InputComponent),
multi: true
}
],
host: {
class: 'input-form'
}
})
export class InputComponent implements OnInit, ControlValueAccessor {
#Input() theme: string = 'material' // 'material' or 'block' or 'inline'
#Input() minlength: number
#Input() maxlength: number
#Input() min: number
#Input() max: number
#Input() pattern: string
// tslint:disable-next-line:no-reserved-keywords
#Input() type: string
#Input() autocomplete: string
#Input() required
#Input() disabled
#Input() readonly
#Input() multiline: boolean
#Input() withCharCount: boolean
#Input() label: string
#Input() name: string
#Input() placeholder: string
#Input() help: string
#Input() hint: string
#Input() bg: string
#Input() color: string
#Input() borderColor: string
#Input() labelColor: string
#Input() myModel: any
ValidationHints = {
required: ' Please fill out this field.',
minlength: ' Field must be at least 3 characters long.',
maxlength: ' Field cannot be more than 24 characters long.',
pattern: ' Please match the requested format.'
}
focus: boolean = false
#ViewChild('inputField')
private _inputField: ElementRef
#ViewChild('inputFieldMultiline')
private _inputFieldMultiline: ElementRef
private _model: string
constructor(private cd: ChangeDetectorRef) {
}
// tslint:disable-next-line:no-empty
onChange: any = () => {
}
// tslint:disable-next-line:no-empty
onTouched: any = () => {
}
ngOnInit() {
this.required = this.required !== undefined && this.required !== false
this.disabled = this.disabled !== undefined && this.disabled !== false
this.readonly = this.readonly !== undefined && this.readonly !== false
this.myModel = ''
}
get model() {
return this._model
}
set model(val: string) {
if (val === this._model) {
return
}
this._model = val
this.onChange(val)
}
writeValue(value: any) {
this._model = value
this.cd.markForCheck()
}
registerOnChange(fn: any): void {
this.onChange = fn
}
registerOnTouched(fn: any): void {
this.onTouched = fn
}
onFocus() {
if (this.readonly || this.disabled) {
return
}
this.focus = true
}
onBlur() {
if (this.readonly || this.disabled) {
return
}
this.focus = false
this.onTouched()
}
_focusInputField() {
if(!this.readonly && !this.disabled) {
if (this.multiline) {
this._inputFieldMultiline.nativeElement.focus()
} else {
this._inputField.nativeElement.focus()
}
}
}
}
<div *ngIf="!multiline">
<input
#inputField
class="input-container__input"
*ngIf="!multiline"
(focus)="onFocus()"
(blur)="onBlur()"
placeholder="{{placeholder}}"
name="{{name}}"
type="{{type || 'text'}}"
pattern="{{pattern}}"
minlength="{{minlength}}"
maxlength="{{maxlength}}"
min="{{min}}"
max="{{max}}"
[required]="required"
autocomplete="{{autocomplete}}"
[disabled]="disabled"
[readonly]="readonly"
[(ngModel)]="model"
#myModel="ngModel"
/>
</div>
<ng-container *ngTemplateOutlet="inputContent"></ng-container>
<div *ngIf="multiline" class="input-container__multiline">
<div class="input-container__input input-container__multiline__place-holder">{{model}}</div>
<textarea
#inputFieldMultiline
class="input-container__input input-container__multiline__input"
[(ngModel)]="model"
(focus)="onFocus()"
(blur)="onBlur()"
placeholder="{{placeholder}}"
name="{{name}}"
minlength="{{minlength}}"
maxlength="{{maxlength}}"
[required]="required"
[disabled]="disabled"
[readonly]="readonly"
#myModel="ngModel"
></textarea>
</div>
</div>
<ng-container *ngTemplateOutlet="inputContent"></ng-container>
{{hint}}
</div>
<ng-template #inputContent>
<div class="input-container__hints" *ngIf="this.theme !== 'inline'">
<div class="input-container__hints--left">
<div *ngIf="myModel && myModel.errors && myModel.touched" class="alert-error">
<div [hidden]="!myModel.errors.required">
<i class="mi mi-warning"></i>
{{ValidationHints.required}}
</div>
<div [hidden]="!myModel.errors.minlength">
<i class="mi mi-warning"></i>
{{ValidationHints.minlength}}
</div>
<div [hidden]="!myModel.errors.maxlength">
<i class="mi mi-warning"></i>
{{ValidationHints.maxlength}}
</div>
<div [hidden]="!myModel.errors.pattern">
<i class="mi mi-warning"></i>
{{ValidationHints.pattern}}
</div>
</div>
</div>
<div class="input-container__hints--right" *ngIf="withCharCount">
{{(model?.length || 0) + (maxlength ? ('/' + maxlength) : '')}}
</div>
</div>
</ng-template>
Notice here that you have deifned multiline as #Input() which means you are passing the multiline into InputComponent from another component.
Use the lifecycle hook OnChanges to detect the changes whenever the value of multiline is injected or updated from the parent component. Modify your code like this -
Add this import statement :
import {OnChanges, SimpleChanges} from '#angular/core';
Add this lines to InputComponent:
export class InputComponent implements OnInit, ControlValueAccesso, OnChanges {
// other code goes here
public multiline: boolean = false; <--- define an initial value
ngOnChanges(changes:SimpleChanges){
if('multiline' in changes){ <---- detect changes and update
this.multiline = changes['multiline'].currentValue;
}
}
}
If you are geting similar errors for other properties defined with #Input() consider implementing the above pattern for them as well.
Hope it helps.

Angular 5 model updates after events completes

The problem
I am using angular 5 and have built a component that has 3 controls
<ss-multiselect-dropdown id="type" formControlName="type" (ngModelChange)="onChange('type')"></ss-multiselect-dropdown>
<input id="caption" type='text' formControlName='caption' (input)="onChange('caption')" />
<custom-uploader formControlName="url" (input)="onChange('url')"></custom-uploader>
when onChange is called from either ss-multiselect-dropdown or input
the model displays the expected value when stepping through the onChange method. But when onChange is called by custom-uploader an old value (the value that existed prior to onChange being called) is still applied to the model.
to be clear:
by expected value I mean the value I select or input for that control
I have tried changing
<custom-uploader formControlName="url" (input)="onChange('url')"></custom-uploader>
to
<custom-uploader formControlName="url" (change)="onChange('url')"></custom-uploader>
and
<custom-uploader formControlName="url" (ngModelChange)="onChange('url')"></custom-uploader>
it seems that the model gets updated after onChange has completed. What have I done wrong?
The code
custom-uploader.component.html
<div class="form-group">
<div class="input-group file-upload">
<input type="file" (change)="fileChange(input)" #input class="file-upload-btn"/>
<input type="text" class="form-control" placeholder="Choose a file..." value="{{file}}">
<i class="fa fa-times delete-file" (click)="removeFile()" *ngIf="file"></i>
<span class="input-group-btn">
<button class="btn btn-primary" type="button"><i class="fa fa-upload"></i></button>
</span>
</div>
</div>
custom-uploader.component.ts
import { Component, ViewEncapsulation, ChangeDetectorRef, ViewChild, Output, EventEmitter, forwardRef, SimpleChanges, OnChanges } from '#angular/core';
import { NG_VALUE_ACCESSOR, ControlValueAccessor, NG_VALIDATORS } from '#angular/forms';
import { Ng2ImgMaxService } from 'ng2-img-max';
#Component({
selector: 'custom-uploader',
encapsulation: ViewEncapsulation.None,
templateUrl: './file-uploader.component.html',
styleUrls: ['./file-uploader.component.scss'],
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FileUploaderComponent), multi: true }
]
})
export class FileUploaderComponent implements OnChanges, ControlValueAccessor {
#ViewChild('input') fileUploaderInput: any;
public file:any;
public propagateChange = (_: any) => {};
constructor(
private _changeDetectorRef: ChangeDetectorRef,
private _ng2ImgMaxService: Ng2ImgMaxService
) { }
writeValue(obj: any): void
{
this.file = obj;
this._changeDetectorRef.detectChanges();
}
registerOnChange(fn: any): void {
this.propagateChange = fn;
}
registerOnTouched(fn: any): void { }
setDisabledState?(isDisabled: boolean): void { }
ngOnChanges(changes: SimpleChanges): void { }
fileChange(input){
const reader = new FileReader();
if (input.files.length) {
this.file = input.files[0].name;
this.propagateChange(this.file);
this.fileUploaderInput.nativeElement.value = "";
}
}
removeFile():void{
this.file = '';
this.propagateChange(this.file);
}
}
The parenthesis syntax like (change) refers to an EventEmitter that emits an event when the value has changed. You have to implement it by yourself in your FileUploaderComponent like this:
#Output() change = new EventEmitter<string>();
...
fileChange(input){
const reader = new FileReader();
if (input.files.length) {
this.file = input.files[0].name;
this.change.next(this.file);
...
}
}
Then where you instantiate it:
Template:
<custom-uploader formControlName="url" (change)="onChange($event)"></custom-uploader>
TypeScript:
onChange(fileName: string) {
//Whatever you want to do with the fileName
}

Angular dynamic forms: Is a bad practice to update values programmatically?

I have implemented a generic dynamic form component:
#Component({
selector: 'dynamic-form',
templateUrl: './dynamic-form.component.html'
})
export class DynamicFormComponent implements OnInit, OnChanges {
#Input() inputs: InputBase<any>[] = [];
#Input() submitLabel: string;
#Input() globalValidator: ValidatorFn | undefined;
#Output() onSubmit: EventEmitter<any> = new EventEmitter<any>();
#Output() onChanges: EventEmitter<any> = new EventEmitter<any>();
#Output() onValid: EventEmitter<boolean> = new EventEmitter<boolean>();
private wasValid: boolean = false;
form: FormGroup;
constructor(private fb: FormBuilder) { }
ngOnInit() {
//Let's get the needed controls to create a FormGroup
this.form = this.generateFormGroup();
}
ngOnChanges() {
this.form = this.generateFormGroup();
}
private generateFormGroup(): FormGroup {
let group: any = {};
this.inputs && this.inputs.forEach(ib => {
ib.addtoFormGroup(group);
});
let form: FormGroup;
if (this.globalValidator) {
form = this.fb.group(group, { validator: this.globalValidator });
} else {
form = this.fb.group(group);
}
form.valueChanges.subscribe(data => {
if (this.form.valid !== this.wasValid) {
this.wasValid = this.form.valid;
this.onValid.emit(this.wasValid);
}
this.onChanges.emit(this.form.value);
});
return form;
}
submit() {
this.onSubmit.emit(this.form.value);
}
isValid(): boolean {
return this.form.valid;
}
getValue() {
return this.form.value;
}
}
The template is the following:
<form *ngIf="form" role="form" class="form-horizontal" (ngSubmit)="submit()" [formGroup]="form">
<div class=" row ibox-content">
<div class="col-md-12">
<div *ngFor="let input of inputs" class="form-group">
<df-input [input]="input" [form]="form" [options]="input.options"></df-input>
</div>
</div>
<div class="row col-md-12">
<button class="btn btn-primary pull-right" type="submit" [disabled]="!form.valid">{{submitLabel| translate}}</button>
</div>
</div>
</form>
As you can see, this component receives a list of items of several classes which extend InputBase and uses them to generate df-input components: The only interesting part of this class for my question is the following method, which helps to fill the object that will be used to create the FormGroup:
addtoFormGroup(groupConf: {[key:string]:any}) {
if (groupConf[this.key]) {
throw new Error('The form already has an input with the same name: '+ this.key);
}
groupConf[this.key]=[this.value,this.validators];
}
Now the df-input component is as follows (simplified):
#Component({
selector: 'df-input',
templateUrl: './input.component.html',
styleUrls: ['./input.component.css']
})
export class InputComponent implements OnInit, OnChanges,DoCheck {
#Input() input: InputBase<any>;
#Input() form: FormGroup;
private wasValid: boolean = false;
differ: any;
constructor(private differs: KeyValueDiffers) {
}
ngOnInit() {
this.differ = this.differs.find(this.input).create();
if (this.input.controlType === 'dropdown') {
let configuration: InputDropdownConfiguration<any>= (this.input as InputDropdownConfiguration<any>);
if (configuration.options && configuration.options.length===1) {
if (this.form) {
(this.form.controls[this.input.key] as AbstractControl).setValue(configuration.options[0].value)
}
}
}
}
ngOnChanges() {
this.ngOnInit();
}
ngDoCheck() {
var changes = this.differ.diff(this.input);
}
get isValid(): boolean {
if (this.form && this.form.controls && this.form.controls[this.input.key]) {
return this.form.controls[this.input.key].valid || !this.form.controls[this.input.key].touched;
}
return true;
}
}
And its template
<div [formGroup]="form" class="form-group" [class.has-error]="!isValid">
<label *ngIf="input.label" [attr.for]="input.key" class="control-label col-sm-{{labelWidth}}">
<ng-container *ngIf="input.isRequired">* </ng-container>{{input.label | translate}}
</label>
<div [ngSwitch]="input.controlType" class="col-sm-{{inputWidth}}">
<input *ngSwitchCase="'text'" class="form-control" [formControlName]="input.key" [type]="input.type"
[name]="input.key" [readonly]="input.readonly" placeholder="{{input.placeholder |translate }}">
<select *ngSwitchCase="'dropdown'" [formControlName]="input.key" class="form-control">
<option *ngFor="let opt of options" [selected]="input.value == opt.value" value="{{opt.value}}">{{opt.text | translate}}</option>
</select>
</div>
<div class="help-block col-md-10 col-md-offset-1" *ngIf="!isValid" [hidden]="!isValid">{{input.errorMsg | translate}}</div>
</div>
This is working, but I'm not sure how to extend it for my next requirement: I need a <select> element which has always a final "Other" option.
If user selects this option, then an input text should appear and more information could be entered.
That means two elements, but I'd like to then to behave as only one: if the "other" option is selected, then the form model should ignore the
select and take the input text value, but I don't want to add both elements to the formGroup.
I am thinking about not adding the attribute [formControlName]="input.key" to any of the elements but programatically, when the selected option changes,
check if it is "Other" and then activate the input field. In any case, I will add the new value to the form with
form.controls[input.key].setValue(<new_value>)
Is this a bad practice? I have faced a lot of issues updating the state of my components and I am trying to keep the smart-dumb components approach, but it is not an easy task for me
You may want to create a Validation service or something similar, and put all of your custom validators in there.
For instance, this is something I have:
import { Injectable } from '#angular/core';
import { FormGroup, AbstractControl } from '#angular/forms';
import { shouldMatch } from './should-match';
#Injectable()
export class Validation {
shouldMatch(...props: string[]) {
return (group: FormGroup): any => shouldMatch(group, ...props);
}
}
should-match.ts:
import { FormGroup } from '#angular/forms';
export function shouldMatch(group: FormGroup, ...props: string[]): any {
const ctrls = group.controls;
const len = props.length;
for (let i = 1; i < len; i++) {
if (ctrls[props[0]]) {
if (ctrls[props[0]].value !== ctrls[props[i]].value) {
return {invalid: true};
}
} else {
throw new Error(`The property '${props[0]}' passed to 'shouldMatch()' was not found on the form group.`);
}
}
return null;
}
And I use it in a place where I want two passwords to match (it takes in properties as strings which represents the form control names in your form group that should all have the same value):
this.form = this.fb.group({
paswords: this.fb.group({
password: [null, Validators.required],
passwordConfirmation: [null, Validators.required]
}, {validator: this.validation.shouldMatch('password', 'passwordConfirmation')}
})

Angular 2 ControlValueAccessor for custom checkbox, How inside to outside value get updated?

I am trying to implement custom component for checkbox using bootstrap 4 checkbox layout.
I am following this link to implement custom component
http://almerosteyn.com/2016/04/linkup-custom-control-to-ngcontrol-ngmodel
I successfully did for radio, but checkbox I am taking input as array of ids and output will be array of ids based on selection.
Checkbox are created based on list of JS model instance.
export class InputProp<T>{
displayName?:String;
order?:Number;
constructor( public checked:boolean=false,
public value:String,
public id:T)
{}
}
But one thing i am not understanding, How angular 2 read inner value and update outside modal
There is writevalue method who read outside component ngModel values.
Is there any method like readValue
Source code
checkbox-button.component.ts
import { InputProp } from './../../models/common.classes';
import { TitleCasePipe } from './../../../pipes/title-case.pipe';
import {
FormGroup,
FormControl,
FormBuilder,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
NG_VALIDATORS,
ValidatorFn
} from '#angular/forms';
// Framework
import { Input,
Output,
Component,
OnInit,
OnDestroy,
forwardRef,
EventEmitter } from "#angular/core";
const noop = () => {
};
export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {
provide: NG_VALUE_ACCESSOR,
useExisting: forwardRef(() => CheckBoxButton),
multi: true
};
#Component({
selector: 'checkbox-button',
styleUrls:['./../buttons.component.scss'],
templateUrl:'./checkbox-button.component.html',
providers:[TitleCasePipe]
})
export class CheckBoxButton implements OnInit, OnDestroy, ControlValueAccessor {
private titleCase:TitleCasePipe=new TitleCasePipe();
//(ngModelChange)="CbaCheckBoxButtonChange($event)"
#Input('label')
private labelName:String;
#Input('list')
private compList:InputProp<Number|String>[];
#Input('isenum')
private isEnumBaseCheckbox:boolean=false;
#Input('enumlist')
private enumComp:any;
#Input('isupcase')
private isUpperCase:boolean=false;
#Input('sortdir')
private sortDir:String='asc';
#Input('labeldir')
private isLabelDisplayDir:String='top';
#Output() onCheckBoxChangeNotify: EventEmitter<(Number|String)[]>;
private prevSelectedVals:(Number|String)[];
private innerVal: any[];
private orderBy:any;
constructor() {
this.onCheckBoxChangeNotify = new EventEmitter<(Number|String)[]>();
}
ngOnInit() {
this.orderBy={fields:['order'],sortDir:this.sortDir};
//console.debug('CbaCheckBoxButton::ngOnInit: ',this.compList,this.labelName);
}
ngOnDestroy(){
//console.debug('CbaCheckBoxButton::ngOnDestory: ');
}
//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 selectedVal(): any {
return this.innerVal;
};
// //set accessor including call the onchange callback
// set selectedVal(v: any) {
// if (v !== this.innerVal) {
// this.innerVal = v;
// this.onChangeCallback(v);
// }
// }
//From ControlValueAccessor interface
writeValue(value: any) {
if (value !== this.innerVal) {
this.innerVal = value;
this.compList.map(f=>f.checked=false);
for(let val in this.innerVal)
this.compList.filter(f=>f.id==val).map(f=>f.checked=true);
// This will initialize checkbox with select or deseclt based on input
}
}
//Set touched on blur
onBlur() {
this.onTouchedCallback();
}
//From ControlValueAccessor interface
registerOnChange(fn: any) {
this.onChangeCallback = fn;
}
//From ControlValueAccessor interface
registerOnTouched(fn: any) {
this.onTouchedCallback = fn;
}
changeCheckbox(checkbox:InputProp<Number>,event:boolean):Boolean {
console.debug('CbaCheckBoxButton::changeCheckbox: ',checkbox,event);
// Below code deselect other if All or Global selected
if(checkbox.id!=-1){
this.compList.filter(f => f.checked && f.id==-1 ).map(f=>f.checked=false);
} else {
this.compList.filter(f => f.checked && f.id!=-1 ).map(f=>f.checked=false);
}
this.innerVal=this.compList.filter(f => f.checked).map(f=>(f.id));;
this.onTouchedCallback();
// let selectedInputs=this.compList.filter(f => f.checked).map(f=>(f.id));
// let me=this;
// if(selectedInputs.length==0){
// this.compList.map(f=>{
// if(f.id==me.prevSelectedVals[0])
// f.checked=true;
// });
// return true;
// }
// // There is small bug here, when try to deselect last record, which is prevented by these code and works perfet
// // But when you jump others and try to deselect again previously tried, it need twice click
// this.prevSelectedVals=selectedInputs;
return false; // this.updateParentListener();
}
// updateParentListener():Boolean {
// this.onCheckBoxChangeNotify.emit(selectedInputs);
// }
}
checkbox-button.component.html
<div class="btn-group d-flex flex-default" *ngIf="isLabelDisplayDir=='left' || isLabelDisplayDir=='right'">
<label class="btn btn-primary radio-label labelName btn-right-border" *ngIf="isLabelDisplayDir=='left'">{{labelName}}</label>
<template [ngIf]="!isEnumBaseCheckbox">
<label class="btn btn-primary radio-label btn-right-border" [ngClass]="{'radio-label-checked':checkbox.checked}"
*ngFor="let checkbox of compList | orderBy:orderBy" >
<input type="checkbox" class="radio-input" (blur)="onBlur()" [value]="checkbox.checked"
[(ngModel)]="checkbox.checked" (ngModelChange)="changeCheckbox(checkbox,$event)">
{{ isUpperCase ? (checkbox.displayName||checkbox.value | uppercase):(checkbox.displayName||checkbox.value | titleCase)}}
</label>
</template>
<label class="btn btn-primary radio-label labelName btn-right-border" *ngIf="isLabelDisplayDir=='right'">{{labelName}}</label>
</div>
<div *ngIf="isLabelDisplayDir=='top' || isLabelDisplayDir=='bottom'">
<div class="btn-group d-flex flex-default" *ngIf="isLabelDisplayDir=='top'">
<label class="btn btn-primary radio-label labelNameTB">{{labelName}}</label><br/>
</div>
<div class="btn-group d-flex flex-default">
<template [ngIf]="!isEnumBaseCheckbox">
<label class="btn btn-primary radio-label btn-right-border" [ngClass]="{'radio-label-checked':checkbox.checked}"
*ngFor="let checkbox of compList | orderBy:orderBy" >
<input type="checkbox" class="radio-input" (blur)="onBlur()" [value]="checkbox.checked"
[(ngModel)]="checkbox.checked" (ngModelChange)="changeCheckbox(checkbox,$event)">
{{ isUpperCase ? (checkbox.displayName||checkbox.value | uppercase):(checkbox.displayName||checkbox.value | titleCase)}}
</label>
</template>
</div>
<div class="btn-group d-flex flex-default" *ngIf="isLabelDisplayDir=='bottom'">
<label class="btn btn-primary radio-label labelNameTB">{{labelName}}</label><br/>
</div>
</div>

Angular 2, rc 4 radio-button to ng-model binding breaks

Angular 2 radio-button to ng-model binding breaks.
Below is my code. It works the first time I change radio buttons. From second time selecting female has no effect.
<radio-button [(ngModel)]="isMale" [value]="true" [label]="resource.mySiteDashboard_Male" name="isMale"></radio-button>
<radio-button [(ngModel)]="isMale" [value]="false" [label]="resource.mySiteDashboard_Female" name="isMale"></radio-button>
<div class="market-profile-content" *ngIf="isMale">
</div>
<div class="market-profile-content" *ngIf="!isMale">
</div>
It worked fine in angular 2 rc-2
below is the template for the component.
<div class="radio">
<input [(ngModel)]="value" type="radio" [id]="'radio-'+guid" [attr.name]="name" [disabled]="disabled" (change)="update()" />
<label [attr.for]="'radio-'+guid">
<span>{{label}}</span>
</label>
</div>
And the code
import {OnInit, Component, Input, Output, EventEmitter, ElementRef, forwardRef, Provider} from '#angular/core'
import {NG_VALUE_ACCESSOR, ControlValueAccessor} from '#angular/forms';
const RADIO_VALUE_ACCESSOR: Provider = new Provider(NG_VALUE_ACCESSOR, {
useExisting: forwardRef(() => RadioButton),
multi: true
});
#Component({
selector: 'radio-button',
templateUrl: './radioButton.html',
providers: [RADIO_VALUE_ACCESSOR]
})
export class RadioButton implements ControlValueAccessor{
#Input() value: boolean;
#Input() label: string;
#Input() name: string;
#Input() disabled: boolean;
#Output() valueChange: EventEmitter<any> = new EventEmitter();
guid: string;
model: any;
checked: boolean;
onModelChange: Function = () => { };
onModelTouched: Function = () => { };
ngOnInit() {
this.guid = this.generateGuid();
}
generateGuid() {
...
}
update() {
if (!this.disabled) {
this.valueChange.emit(this.value);
this.onModelChange(this.value);
}
}
writeValue(model: any): void {
this.model = model;
this.checked = (this.model == this.value);
}
registerOnChange(func: Function): void {
this.onModelChange = func;
}
registerOnTouched(func: Function): void {
this.onModelTouched = func;
}
}

Resources