Can the Polymer paper-dropdown-menu be bound using ngControl? - forms

I am trying to create a form in Angular2 using the Polymer paper-dropdown-menu control. Is there a way to bind the selected value of the dropdown to the control in my component? I have tried everything with no luck. Has anyone gotten over this hurdle?
An example of a working paper-input is:
template:
<paper-input type="password"
ngControl="password"
ngDefaultControl>
</paper-input>
component:
constructor(private fb:FormBuilder) {
this.loginForm = fb.group({
password: new Control("")
});
}
Is there something similar for paper-dropdown-menu? Either binding to the value or the text itself would be fine. Thanks!

You need a custom ControlValueAccessor. I didn't succeed using a ControlValueAccessor for the paper-dropdown-menu itself but for the paper-menu or paper-listbox inside the paper-dropdown-menu like
const PAPER_MENU_VALUE_ACCESSOR = new Provider(
NG_VALUE_ACCESSOR, {useExisting: forwardRef(() => PaperMenuControlValueAccessor), multi: true});
#Directive({
selector: 'paper-listbox',
host: {'(iron-activate)': 'onChange($event.detail.selected)'},
providers: [PAPER_MENU_VALUE_ACCESSOR]
})
export class PaperMenuControlValueAccessor implements ControlValueAccessor {
onChange = (_:any) => {
};
onTouched = () => {
};
constructor(private _renderer:Renderer, private _elementRef:ElementRef) {
console.log('PaperMenuControlValueAccessor');
}
writeValue(value:any):void {
//console.log('writeValue', value);
this._renderer.setElementProperty(this._elementRef.nativeElement, 'selected', value);
}
registerOnChange(fn:(_:any) => {}):void {
this.onChange = fn;
}
registerOnTouched(fn:() => {}):void {
this.onTouched = fn;
}
}
See also
ngModel Binding on Polymer dropdown (Angular2)
Bind angular 2 model to polymer dropdown

Related

Angular 2 | How to handle input type file in FormControl?

Good day,
How can i handle input type file in formControl? im using reactive form but when i get the value of my form it returns null value on my <input type="file">??
You need to write your own FileInputValueAccessor. Here is the plunker and the code:
#Directive({
selector: 'input[type=file]',
providers: [
{
provide: NG_VALUE_ACCESSOR,
useExisting: FileValueAccessorDirective,
multi: true
}
]
})
export class FileValueAccessorDirective implements ControlValueAccessor {
onChange;
#HostListener('change', ['$event.target.value']) _handleInput(event) {
this.onChange(event);
}
constructor(private element: ElementRef, private render: Renderer2) { }
writeValue(value: any) {
const normalizedValue = value == null ? '' : value;
this.render.setProperty(this.element.nativeElement, 'value', normalizedValue);
}
registerOnChange(fn) { this.onChange = fn; }
registerOnTouched(fn: any) { }
nOnDestroy() { }
}
And then you will be able to get updates like this:
#Component({
moduleId: module.id,
selector: 'my-app',
template: `
<h1>Hello {{name}}</h1>
<h3>File path is: {{path}}</h3>
<input type="file" [formControl]="ctrl">
`
})
export class AppComponent {
name = 'Angular';
path = '';
ctrl = new FormControl('');
ngOnInit() {
this.ctrl.valueChanges.subscribe((v) => {
this.path = v;
});
}
}
there is no way to handle this in angular form-control.
we can provide some hack to make this work if you want to upload the image.
just add the <input type=file> as form control on which user can add the file and acter grabbing the file we can change it to the base64code and then assign that value to the hidden field of our main form.
then we can store the image file in that way.
else you can go for the ng-file-upload moduleng file upload

How can I use props to auto-populate editable redux-form fields in React?

I'm new to React so I've tried to show as much code as possible here to hopefully figure this out! Basically I just want to fill form fields with properties from an object that I fetched from another API. The object is stored in the autoFill reducer. For example, I would like to fill an input with autoFill.volumeInfo.title, where the user can change the value before submitting if they want.
I used mapDispatchtoProps from the autoFill action creator, but this.props.autoFill is still appearing as undefined in the FillForm component. I'm also confused about how to then use props again to submit the form. Thanks!
My reducer:
import { AUTO_FILL } from '../actions/index';
export default function(state = null, action) {
switch(action.type) {
case AUTO_FILL:
return action.payload;
}
return state;
}
Action creator:
export const AUTO_FILL = 'AUTO_FILL';
export function autoFill(data) {
return {
type: AUTO_FILL,
payload: data
}
}
Calling the autoFill action creator:
class SelectBook extends Component {
render() {
return (
....
<button
className="btn btn-primary"
onClick={() => this.props.autoFill(this.props.result)}>
Next
</button>
);
}
}
....
function mapDispatchToProps(dispatch) {
return bindActionCreators({ autoFill }, dispatch);
}
export default connect(mapStateToProps, mapDispatchToProps)(SelectBook);
And here is the actual Form where the issues lie:
import React, { Component } from 'react';
import { connect } from 'react-redux';
import { reduxForm } from 'redux-form';
import { createBook } from '../actions/index;
class FillForm extends Component {
constructor(props) {
super(props);
this.state = {value: ''};
this.handleChange = this.handleChange.bind(this);
}
onSubmit(props) {
this.props.createBook(props)
}
handleChange(event) {
this.setState({value: event.target.value});
}
render() {
const { fields: { title }, handleSubmit } = this.props;
return (
<form {...initialValues} onSubmit={handleSubmit(this.onSubmit.bind(this))}>
<input type="text" className="form-control" name="title" {...title} />
<button type="submit">Submit</button>
</form>
)
}
export default reduxForm({
form: 'AutoForm',
fields: ['title']
},
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}), {createBook})(FillForm)
I think you're mixing up connect and reduxForm decorators in the actual form component. Currently your code looks like this (annotations added by me):
export default reduxForm({
// redux form options
form: 'AutoForm',
fields: ['title']
},
// is this supposed to be mapStateToProps?
state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
}),
/// and is this mapDispatchToProps?
{createBook})(FillForm)
If this is the case, then the fix should be as simple as using the connect decorator as it should be (I also recommend separating this connect props to their own variables to minimize confusions like this):
const mapStateToProps = state => ({
initialValues: {
title: state.autoFill.volumeInfo.title
}
})
const mapDispatchToProps = { createBook }
export default connect(mapStateToProps, mapDispatchToProps)(
reduxForm({ form: 'AutoForm', fields: ['title'] })(FillForm)
)
Hope this helps!

Angular 2: Custom Input Component with Validation (reactive/model driven approach)

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.
I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.
Plunkr
//our root app component
import {Component, Input} from '#angular/core'
import {
FormControl,
FormGroup,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators
} from '#angular/forms';
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [formControl]="childControl">
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor {
childControl = new FormControl('', Validators.maxLength(10));
writeValue(value: any) {
this.childControl.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
this.childControl.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
#Component({
selector: 'my-app',
template: `
<div>
<h4>Hello {{name}}</h4>
<form [formGroup]="form" (ngSubmit)="sayHello()">
<my-child formControlName="username"></my-child>
<button type="submit">Register</button>
</form>
{{form.value | json }}
</div>
`
})
export class App {
form = new FormGroup({
username: new FormControl('username', Validators.required)
});
constructor() {
this.name = 'Angular2';
}
sayHello() {
console.log(this.form.controls['username'])
}
}
I have no clue how to solve this problem in a proper way
There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:
{
provide: NG_VALIDATORS,
useExisting: CustomInputComponent,
multi: true
}
your component needs to implement the Validator interface:
class CustomInputComponent implements ControlValueAccessor, Validator,...
and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:
validate(control: AbstractControl): ValidationErrors | null {
return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
}
there is also another to sync the validators when their inputs change:
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}

How to perform async validation using reactive/model-driven forms in Angular 2

I have an email input and I want to create a validator to check, through an API, if the entered email it's already in the database.
So, I have:
A validator directive
import { Directive, forwardRef } from '#angular/core';
import { Http } from '#angular/http';
import { NG_ASYNC_VALIDATORS, FormControl } from '#angular/forms';
export function validateExistentEmailFactory(http: Http) {
return (c: FormControl) => {
return new Promise((resolve, reject) => {
let observable: any = http.get('/api/check?email=' + c.value).map((response) => {
return response.json().account_exists;
});
observable.subscribe(exist => {
if (exist) {
resolve({ existentEmail: true });
} else {
resolve(null);
}
});
});
};
}
#Directive({
selector: '[validateExistentEmail][ngModel],[validateExistentEmail][formControl]',
providers: [
Http,
{ provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => ExistentEmailValidator), multi: true },
],
})
export class ExistentEmailValidator {
private validator: Function;
constructor(
private http: Http
) {
this.validator = validateExistentEmailFactory(http);
}
public validate(c: FormControl) {
return this.validator(c);
}
}
A component
import { Component } from '#angular/core';
import { FormGroup, FormBuilder, Validators } from '#angular/forms';
import { ExistentEmailValidator } from '../../directives/existent-email-validator';
#Component({
selector: 'user-account',
template: require<string>('./user-account.component.html'),
})
export class UserAccountComponent {
private registrationForm: FormGroup;
private registrationFormBuilder: FormBuilder;
private existentEmailValidator: ExistentEmailValidator;
constructor(
registrationFormBuilder: FormBuilder,
existentEmailValidator: ExistentEmailValidator
) {
this.registrationFormBuilder = registrationFormBuilder;
this.existentEmailValidator = existentEmailValidator;
this.initRegistrationForm();
}
private initRegistrationForm() {
this.registrationForm = this.registrationFormBuilder.group({
email: ['', [this.existentEmailValidator]],
});
}
}
And a template
<form novalidate [formGroup]="registrationForm">
<input type="text" [formControl]="registrationForm.controls.email" name="registration_email" />
</form>
A've made other validator this way (without the async part) and works well. I think te problem it's related with the promise. I'm pretty sure the code inside observable.subscribe it's running fine.
What am I missing?
I'm using angular v2.1
Pretty sure your problem is this line:
...
email: ['', [this.existentEmailValidator]],
...
You're passing your async validator to the synchronous validators array, I think the way it should be is this:
...
email: ['', [], [this.existentEmailValidator]],
...
It would probably be more obvious if you'd use the new FormGroup(...) syntax instead of FormBuilder.

Angular2 (rc.5) setting a form to invalid with a custom validator

I am using a template-driven form with a custom validator on one of the fields :
<button type="button" (click)="myForm.submit()" [disabled]="!myForm.valid" class="btn">Save</button>
<form #myForm="ngForm" (ngSubmit)="submit()">
...
<input type="text"
class="validate"
[(ngModel)]="myDate"
name="myDate"
ngControl="myDate"
myValidator/>
</form>
myValidator :
import { Directive, forwardRef } from '#angular/core'
import { NG_VALIDATORS, FormControl, Validator } from '#angular/forms'
function check(s: string) {
...
}
function customValidation() {
return (c: FormControl) => {
return check(c.value) ? null : {
myValidator: false
}
}
}
#Directive({
selector: '[myValidator ][ngModel],[myValidator ][formControl]',
providers: [
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => MyValidator), multi: true }
]
})
export class MyValidator implements Validator {
validator: Function
constructor() {
this.validator = customValidation()
}
validate(c: FormControl) {
return this.validator(c)
}
}
Everything is working just fine on the field. The only issues comes when the validator set the field to invalid, the form isn't set to invalid and thus the save button isn't disabled. I can't exactly get why. I guess I forgot a link between the validator and the form.
I am using angular_forms 0.3.0
Here is a plunkr : http://plnkr.co/edit/qUKYGFNLyjh6mNiqYY5I?p=preview which really seems to work... (rc.4 though)
I have put your code in plunkr.
http://plnkr.co/edit/qUKYGFNLyjh6mNiqYY5I?p=preview
And it works just fine. You might check with other parts of your code. Specifically I have my own check function.
function check(s: string) {
if(s.length > 0){
return true;
}else{
return false;
}
}
Have you initialized the myDate value? If it's not initialized i got a valid form on start.
ngOnInit() {
this.myDate = ''
}