离子 2:在页面内的 FORM 中添加自定义输入组件/最终目标:集成“cordova-plugin-datepicker";离子 2 形式 [英] ionic 2: add custom input component in a FORM within a page / Final goal: integrate "cordova-plugin-datepicker" in Ionic 2 Form

查看:22
本文介绍了离子 2:在页面内的 FORM 中添加自定义输入组件/最终目标:集成“cordova-plugin-datepicker";离子 2 形式的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在离子 2 版本中:

  • Cordova CLI:6.4.0,
  • 离子框架版本:2.0.0-rc.0,
  • 离子 CLI 版本:2.1.0,
  • Ionic 应用程序库版本:2.1.0-beta.1,操作系统:
  • 节点版本:v6.7.0

使用 Ionic 2 FORM,输入: 恰好很慢 (见这里).

我想绕过它并使用 "cordova-plugin-datepicker" 代替.我有很多关于它的问题以使其工作.但我将从我需要​​实现的第一步开始:实现可用作 标记的自定义选择器.

从这里开始,我们将尝试通过另一个组件实现标签 .

我在此处发现了类似问题.它告诉 import:import {IONIC_DIRECTIVES} from 'ionic-angular'; 并在 @Component 注释中添加元数据:directives: [IONIC_DIRECTIVES].但是在 Angular 2 文档中,元数据 directives 不再存在.如果我尝试这样做,我会得到一个错误.

现在我的代码:

我有一个用户表单页面:

import { Component } from '@angular/core';从@angular/forms"导入 { FormBuilder, FormGroup, Validators };从离子角"导入 { NavController, NavParams };import { NativeDatePickerTag } from '../../custom-components/native-date-picker/native-date-picker';@成分({选择器:'用户表单',templateUrl: 'user-form.html',提供者:[验证器]})导出类 UserFormPage {私有只读 PAGE_TAG:string = "UserFormPage";公开生日:任何;公共用户表单:表单组;构造函数(公共导航控件:导航控制器,公共导航参数:导航参数,公共 fb:FormBuilder,公共验证器:验证器){}公共更新用户数据 = () =>{console.log(this.userForm.value);}ionViewDidLoad(){console.log(this.PAGE_TAG + " ionViewDidLoad() 开始");this.userForm = this.fb.group({生日:[this.birthdate,Validators.required],});}}

在我的user-form.html"中,它看起来像这样:

 <离子内容><form (ngSubmit)="updateUserData()" [formGroup] = "userForm" ><离子项目><离子标签堆叠>生日</离子标签><native-date-picker [controlName]="生日"></native-date-picker></ion-item><button ion-button type="submit" block>提交</button></表单></离子含量>

还有我的自定义组件 NativeDatePickerTag(同样,这是尚未实现 cordova-plugin-datepicker 的概念证明):

import { Component, Input, ViewChild, ElementRef } from '@angular/core';从离子角"导入{平台};从'@angular/forms' 导入 { FormGroup, FormControl };@成分({选择器:'本机日期选择器',模板:`<ion-datetime [formControlName]='this._controlName'></ion-datetime>`})导出类 NativeDatePickerTag {私有只读 COMPONENT_TAG = "NativeDatePickerObj";公共_controlName:表单控件;@Input() 设置控件名称(newControl: FormControl){this._controlName = newControl;}构造函数(公共平台:平台){}}

如果我像这样运行代码,它会在 console.log 中显示:

<块引用>

formControlName 必须与父 formGroup 指令一起使用

我不明白为什么它不考虑 formGroup 选择器 native-date-picker 嵌入在user-form.html"中.因此,我尝试从customer-form.html"传递 formGroup 以纠正此错误.

在'user-form.html'中我已经改变了,<native-date-picker [controlName]="birthday"></native-date-picker>和:<native-date-picker [groupName]="userForm" [controlName]="birthday"></native-date-picker>

在 NativeDatePickerTag 中,我更改了注释:

@Component({选择器:'本机日期选择器',模板:`

<ion-datetime [formControlName]='this._controlName'></ion-datetime>

`})

我在我的类 NativeDatePickerTag 中添加了以下内容:公共_formGroup:表单组;

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

现在我进入console.log:

<块引用>

找不到具有未指定名称属性的控件

我真的不明白我做错了什么.任何有这方面经验的人都可以给我一些指导吗?

解决方案

我找到了一个解决方案,关键是要了解它的工作原理 ControlValueAccessor 接口

我是通过阅读这些链接做到的:

这里要求的是代码:

native-date-picker.ts:

import { Component, Input, Output, ViewChild, ElementRef, forwardRef, EventEmitter } from '@angular/core';从@angular/forms"导入 { FormGroup, FormControl, NG_VALUE_ACCESSOR, NG_VALIDATORS, ControlValueAccessor };import { FormValidators } from '../../form-validators/form-validators';从离子角"导入{平台};从'ionic-native'导入{DatePicker};从'../../services/translation/translation'导入{翻译服务};从'moment/min/moment-with-locales.min.js'导入时刻;导出 const CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR: any = {提供:NG_VALUE_ACCESSOR,useExisting: forwardRef(() => NativeDatePickerTag),多:真};声明 var datePicker: any;@成分({选择器:'本机日期选择器',templateUrl: 'native-date-picker.html',提供者:[CUSTOM_INPUT_CONTROL_VALUE_ACCESSOR]})导出类 NativeDatePickerTag 实现 ControlValueAccessor {私有只读 COMPONENT_TAG = "NativeDatePickerTag";//内部数据模型公共日期值:任何 = '';公共选择器类型=空;公共_labelForNDP:任何;公共 displayFormatForIonDateTime:string;公共 ionDateTimeMonthsShort:string;公共 ionDateTimeMonthsLong:string;@Input() submitAttempt;@Input() 控件;@Input() 设置 labelForNDP(value:any){this._labelForNDP = 值;console.log("labelForNDP : " + value);}@Output() onChange: EventEmitter= new EventEmitter();//设置触摸ionChangeonTouched(){console.log(this.COMPONENT_TAG + " onTouched() 开始");this.control._touched=true;}//来自ControlValueAccessor接口写值(值:任何){console.log(this.COMPONENT_TAG + " writeValue("+value+") 开始");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+")");让 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);} 别的 {dateToBeDisplayed="";}返回日期ToBeDisplayed;}更新日期(事件:任何){console.log(this.COMPONENT_TAG + " updateDate() 开始");控制台信息(事件);let newValue = "我是新值";let dateToSetOn = (new moment(event)).format('YYYY-MM-DD');console.log(this.COMPONENT_TAG + " updateDate() 即将返回 " + dateToSetOn);this.onTouched();this.onChange.next(dateToSetOn);}//来自ControlValueAccessor接口registerOnChange(fn: 任何) {console.log(this.COMPONENT_TAG + " registerOnChange() 开始");控制台信息(fn);this.onChange.subscribe(fn);}//来自ControlValueAccessor接口registerOnTouched(fn: any) {//留空}//获取带有# 的元素@ViewChild("nativeDatePicker") nativeDatePicker: ElementRef;@ViewChild("ionDatePicker") ionDatePicker: ElementRef;构造函数(公共平台:平台,公共翻译:翻译服务){console.log(this.COMPONENT_TAG + "constructor() 开始");控制台信息(这个);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();}私有 setFieldWhenPlatformReady = () =>{this.platform.ready().then(() => {if(this.platform.is('android')){this.pickerType = "android";} else if (this.platform.is('ios')) {//ios案例:尚未完成} else if (this.platform.is('core')) {this.pickerType = "核心";}});}公共日期输入管理 = () =>{console.log(this.COMPONENT_TAG + " dateInputManagement() 开始");let dateToUseOnOpening = (moment(this.dateValue,'YYYY-MM-DD').isValid())?new Date(moment(this.dateValue,'YYYY-MM-DD')):new Date();控制台信息(dateToUseOnOpening);让选项 = {日期:dateToUseOnOpening,模式:'日期',androidTheme:datePicker.ANDROID_THEMES.THEME_HOLO_LIGHT};DatePicker.show(options).then((日期) =>{让 lang = this.trans.getCurrentLang();this.writeValue(新时刻(日期));this.updateDate(新时刻(日期));}).catch( (error) => {//仅安卓});}}

和 native-date-picker.html:

<离子标签堆叠>{{_labelForNDP}}</ion-label><ion-datetime #ionDatePicker [displayFormat]="displayFormatForIonDateTime" [monthShortNames]="ionDateTimeMonthsShort" [monthNames]="ionDateTimeMonthsLong" *ngIf="pickerType=='core' || pickerType=='ios'" name="生日" [ngModel]="dateValue" (ngModelChange)="updateDate($event)" [class.invalid]="!control.valid && (control.touched||submitAttempt)"></ion-日期时间><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>

并且在包含调用它的表单的组件的 HTML 模板中,为了尊重需要提供给 NativeDatePicker 类的 @input,它必须如下所示:

 </native-date-picker>

In ionic 2 version:

  • Cordova 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

With Ionic 2 FORM, the input: <ion-datetime> happens to be slow (see here).

I want to go around it and use the "cordova-plugin-datepicker" instead. I have many question about it to make it work. But I'll start here with the first step I need to achieve: To implement a custom selector that can be use as a <ion-[something for a form input]> tag.

To start with here, we are just going to try to implement the tag <ion-datetime> thru another component.

I've found similar issue here. It tells to import:import {IONIC_DIRECTIVES} from 'ionic-angular'; and to add in the @Component annotation the metadata: directives: [IONIC_DIRECTIVES]. But in Angular 2 documentation the metadata directives does not exist anymore. And I get an error if I try that.

Now my code:

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 a proof of concept not yet implementing the cordova-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 the code like that, it tells in the console.log:

formControlName must be used with a parent formGroup directive

I don't understand why it does not take into account the formGroup the selector native-date-picker is embedded in inside 'user-form.html'. So I've tried to pass the formGroup from 'customer-form.html' to correct this error.

In 'user-form.html' I've changed, <native-date-picker [controlName]="birthday"></native-date-picker> with: <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 inside my class NativeDatePickerTag the following: public _formGroup: FormGroup;

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

Now I get in console.log:

Cannot find control with unspecified name attribute

I really don't understand what I am doing wrong. Could anyone, with experience regarding this topic,give me some directions?

解决方案

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

I did that by reading thru those link:

As requested here is the code:

The native-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 a form that calls it, to respect the @input that need to be given to the class NativeDatePicker, it must look like that:

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

这篇关于离子 2:在页面内的 FORM 中添加自定义输入组件/最终目标:集成“cordova-plugin-datepicker";离子 2 形式的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆