Ionic 2: add a custom input component to the FORM within the page / Final goal: integrate the "cordova-plugin-datepicker" into the Ionic 2 Form

In version with ionic 2:

  • Cordoba CLI: 6.4.0,
  • Ionic Framework Version: 2.0.0-rc.0,
  • Ionic CLI Version: 2.1.0,
  • Ionic App Lib Version: 2.1.0-beta.1, OS:
  • Node Version: v6.7.0

When using Ionic 2 FORM, the input: <ion-datetime> is slow ( see here ) .

I want to get around it and use cordova-plugin-datepicker instead . I have a lot of questions to make it work. But I'll start with the first step that I need to achieve: To implement a custom selector that can be used as a <ion-[something for a form input]> .

To get started here, we just try to implement the <ion-datetime> through another component .

I found a similar problem here . It tells import: import {IONIC_DIRECTIVES} from 'ionic-angular'; and add the metadata: directives: [IONIC_DIRECTIVES] . But in Angular 2 documentation, directives metadata no longer exists. And I get an error if I try.

Now my code is:

I have a user form page:

 import { Component } from '@angular/core'; import { FormBuilder, FormGroup, Validators } from '@angular/forms'; import { NavController, NavParams } from 'ionic-angular'; import { NativeDatePickerTag } from '../../custom-components/native-date-picker/native-date-picker'; @Component({ selector:'user-form', templateUrl: 'user-form.html', providers: [Validators] }) export class UserFormPage { private readonly PAGE_TAG:string = "UserFormPage"; public birthdate:any; public userForm:FormGroup; constructor(public navCtrl: NavController, public navParams: NavParams, public fb:FormBuilder, public validators:Validators){} public updateUserData = () => { console.log(this.userForm.value); } ionViewDidLoad(){ console.log(this.PAGE_TAG + " ionViewDidLoad() starts"); this.userForm = this.fb.group({ birthday: [this.birthdate,Validators.required], }); } } 

In my "user-form.html" it looks like this:

  <ion-content> <form (ngSubmit)="updateUserData()" [formGroup] = "userForm" > <ion-item> <ion-label stacked>Birthdate</ion-label> <native-date-picker [controlName]="birthday"></native-date-picker> </ion-item> <button ion-button type="submit" block>Submit</button> </form> </ion-content> 

And my custom component NativeDatePickerTag (again, this is a proof of concept, not yet implementing the cordin-plugin-datepicker):

 import { Component, Input, ViewChild, ElementRef } from '@angular/core'; import { Platform } from 'ionic-angular'; import { FormGroup, FormControl } from '@angular/forms'; @Component({ selector: 'native-date-picker', template: ` <ion-datetime [formControlName]='this._controlName'></ion-datetime> ` }) export class NativeDatePickerTag { private readonly COMPONENT_TAG = "NativeDatePickerObj"; public _controlName: FormControl; @Input () set controlName(newControl: FormControl){ this._controlName = newControl; } constructor(public platform:Platform){ } } 

If I run such code, it reports to console.log:

formControlName should be used with the parent formGroup directive

I do not understand why it does not take into account formGroup native-date-picker selector is embedded inside 'user-form.html'. So I tried passing formGroup from 'customer-form.html' to fix this error.

In 'user-form.html' I changed, <native-date-picker [controlName]="birthday"></native-date-picker> from: <native-date-picker [groupName]="userForm" [controlName]="birthday"></native-date-picker>

And in NativeDatePickerTag I changed the annotation with:

 @Component({ selector: 'native-date-picker', template: `<div [formGroup]='this._formGroup'> <ion-datetime [formControlName]='this._controlName'></ion-datetime> </div> ` }) 

And I added the following to my NativeDatePickerTag class: public _formGroup: FormGroup;

  @Input () set groupName(newGroup: FormGroup){ this._formGroup = newGroup; } 

Now I get in console.log:

Cannot find control with undefined name attribute

I really don't understand what I'm doing wrong. Can someone, having experience in this topic, give me several directions?

0
source share
1 answer

I found a solution, the key was to understand how the ControlValueAccessor Interface works

I did this by reading this link:

In accordance with the request, the code is given:

Original-date-picker.ts:

 import { Component, Input, Output, ViewChild, ElementRef, forwardRef, EventEmitter } from '@angular/core'; import { FormGroup, FormControl, NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor } from '@angular/forms'; import { FormValidators } from '../../form-validators/form-validators'; import { Platform } from 'ionic-angular'; import { DatePicker } from 'ionic-native'; import { TranslationService } from '../../services/translation/translation'; import moment from 'moment/min/moment-with-locales.min.js'; export const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = { provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => NativeDatePickerTag), multi: true }; declare var datePicker: any; @Component({ selector: 'native-date-picker', templateUrl: 'native-date-picker.html', providers:[CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR] }) export class NativeDatePickerTag implements ControlValueAccessor { private readonly COMPONENT_TAG = "NativeDatePickerTag"; //The internal data model public dateValue: any = ''; public pickerType=null; public _labelForNDP:any; public displayFormatForIonDateTime:string; public ionDateTimeMonthsShort:string; public ionDateTimeMonthsLong:string; @Input() submitAttempt; @Input() control; @Input () set labelForNDP(value:any){ this._labelForNDP = value; console.log("labelForNDP : " + value); } @Output () onChange: EventEmitter<any> = new EventEmitter(); //Set touched on ionChange onTouched(){ console.log(this.COMPONENT_TAG + " onTouched() starts"); this.control._touched=true; } //From ControlValueAccessor interface writeValue(value: any) { console.log(this.COMPONENT_TAG + " writeValue("+value+") starts"); if (value !== undefined || value !== null) { this.dateValue = (new moment(value)).format('YYYY-MM-DD'); } console.log(this.COMPONENT_TAG + " writeValue("+value+") this.dateValue " + this.dateValue); } diplayDateAccordingToSettings(date:any){ console.log(this.COMPONENT_TAG + " diplayDateAccordingToSettings("+date+")"); let dateToBeDisplayed:any; if(moment(date,'YYYY-MM-DD').isValid()){ dateToBeDisplayed = (new moment(date)).locale(this.trans.getCurrentLang()).format(this.displayFormatForIonDateTime); console.log(this.COMPONENT_TAG + " diplayDateAccordingToSettings("+date+")" + " GIVES " + dateToBeDisplayed); } else { dateToBeDisplayed=""; } return dateToBeDisplayed; } updateDate(event:any) { console.log(this.COMPONENT_TAG + " updateDate() starts"); console.info(event); let newValue = "I'm new value"; let dateToSetOn = (new moment(event)).format('YYYY-MM-DD'); console.log(this.COMPONENT_TAG + " updateDate() about to return " + dateToSetOn); this.onTouched(); this.onChange.next(dateToSetOn); } //From ControlValueAccessor interface registerOnChange(fn: any) { console.log(this.COMPONENT_TAG + " registerOnChange() starts"); console.info(fn); this.onChange.subscribe(fn); } //From ControlValueAccessor interface registerOnTouched(fn: any) { //leave it empty } // get the element with the # on it @ViewChild("nativeDatePicker") nativeDatePicker: ElementRef; @ViewChild("ionDatePicker") ionDatePicker: ElementRef; constructor(public platform:Platform, public trans:TranslationService){ console.log(this.COMPONENT_TAG + " constructor() starts"); console.info(this); this.displayFormatForIonDateTime = moment.localeData(this.trans.getCurrentLang())._longDateFormat['LL']; this.ionDateTimeMonthsShort = moment.localeData(this.trans.getCurrentLang()).monthsShort(); this.ionDateTimeMonthsLong = moment.localeData(this.trans.getCurrentLang()).months(); this.setFieldWhenPlatformReady(); } private setFieldWhenPlatformReady = () => { this.platform.ready().then(() => { if(this.platform.is('android')){ this.pickerType = "android"; } else if (this.platform.is('ios')) { // ios case: NOT DONE YET } else if (this.platform.is('core')) { this.pickerType = "core"; } } ); } public dateInputManagement = () => { console.log(this.COMPONENT_TAG + " dateInputManagement() starts"); let dateToUseOnOpening = (moment(this.dateValue,'YYYY-MM-DD').isValid())?new Date(moment(this.dateValue,'YYYY-MM-DD')):new Date(); console.info(dateToUseOnOpening); let options = { date: dateToUseOnOpening, mode: 'date', androidTheme: datePicker.ANDROID_THEMES.THEME_HOLO_LIGHT }; DatePicker.show(options).then( (date) => { let lang = this.trans.getCurrentLang(); this.writeValue(new moment(date)); this.updateDate(new moment(date)); }).catch( (error) => { // Android only }); } } 

And native-date-picker.html:

 <ion-item> <ion-label stacked>{{_labelForNDP}}</ion-label> <ion-datetime #ionDatePicker [displayFormat]="displayFormatForIonDateTime" [monthShortNames]="ionDateTimeMonthsShort" [monthNames]="ionDateTimeMonthsLong" *ngIf="pickerType=='core' || pickerType=='ios'" name="birthday" [ngModel]="dateValue" (ngModelChange)="updateDate($event)" [class.invalid]="!control.valid && (control.touched||submitAttempt)"></ion-datetime> <ion-input #nativeDatePicker type="text" disabled=true (click)="dateInputManagement()" *ngIf="pickerType=='android'" name="birthday" [ngModel]="diplayDateAccordingToSettings(dateValue)" (ngModelChange)="updateDate($event)" [class.invalid]="!control.valid && (control.touched||submitAttempt)"></ion-input> </ion-item> 

And in the HTML template of the component containing the form calling it to respect @input , which should be assigned to the NativeDatePicker class, it should look like this:

  <native-date-picker [labelForNDP]="LABEL" #nativeDatePickerOnUserPage formControlName="date" [control]="userForm.controls['date']" [submitAttempt]=submitAttempt> </native-date-picker> 
0
source

All Articles