Angular 7 没有日期时间选择器吗?

Are there no DateTime picker for Angular 7?

我找不到 Angular 7 的日期时间选择器。 所以我决定结合日期选择器和时间选择器

https://ng-bootstrap.github.io/#/components/datepicker

https://ng-bootstrap.github.io/#/components/timepicker

<ng-template #dateTimePicker>
  <ngb-datepicker #createdStartDate name="datepicker"></ngb-datepicker>
  <ngb-timepicker #createdStartTime name="timepicker" [meridian]="true"></ngb-timepicker>
</ng-template>

<form [formGroup]="managePromotionsForm" 
    <div class="col-md-6">
      <div class="row form-group">
        <label class="col-md-4 control-label" for="createdStartDate" translate="">Created From </label>
        <div class="col-md-6">
          <div class="input-group">
            <input readOnly class="form-control" id="createdStartDate" placeholder="From Date"
              [formControl]="controls['createdStartDate']">
              
              
            <div class="input-group-append">
              <button class="btn btn-outline-secondary calendar" [ngbPopover]="dateTimePicker"  type="button"></button>
            </div>
          </div>
        </div>
      </div>
    </div>
</form>

这是我目前所拥有的

现在如何在 createdStartDate 文本框上显示选定的日期和时间?

如果您想创建一个可以绑定到的表单元素。更好的方法是创建一个单独的组件并实现 control value accessor.

然后就可以把组件当做表单元素,绑定ngModal或者表单控件了

如果您愿意,可以结合使用 ngbDropDown、ngbDatePicker 和 ngbTimePicker

为此你需要两个变量和一个 getter

  date: any;
  time:any= {hour:0,minute:0};

  _value;
  label;
  ngOnInit()
  {
    this.getDatetime()
  }
  getDatetime() {
    let value = null;
    if (!this.date) {
      if (!this.time) value = "yyyy/MM/dd hh:mm";
      else
        value =
          "yyyy/MM/dd " +
          ("0" + this.time.hour).slice(-2) +
          ":" +
          ("0" + this.time.minute).slice(-2);
    }
    if (!value) {
      value = new Date(Date.UTC(
        this.date.year,
        this.date.month - 1,
        this.date.day,
        this.time ? this.time.hour : 0,
        this.time ? this.time.minute : 0
      );
      this._value=value;
   } else 
      this._value=null

   this.form.get("control").setValue(this._value);
   this.label=value;
  }

<form [formGroup]="form">
  <div ngbDropdown>
  <button class="datepicker btn btn-link"  ngbDropdownToggle>{{_value?(_value|date:'medium'):label}}</button>
      <div ngbDropdownMenu >
        <ngb-datepicker #dp [(ngModel)]="date" (dateSelect)="getDatetime()"[ngModelOptions]="{standalone:true}" ></ngb-datepicker>
        <ngb-timepicker [ngModel]="time" (ngModelChange)="time=$event;getDatetime()"[ngModelOptions]="{standalone:true}"></ngb-timepicker>
      </div>
      </div>
  <button class="btn btn-primary">submit</button>

参见stackblitz

注意:在这种情况下,我们将创建一个自定义表单控件以不产生依赖性

Update 出于好奇,在 stackblitz 我制作了自定义表单控件

作为功能请求,有一个未解决的问题: https://github.com/ng-bootstrap/ng-bootstrap/issues/2086

日期选择器和时间选择器可以与一些自定义代码结合使用。

这是一个使用带有按钮的文本输入来打开日期选择器并在时间选择器之间切换的示例。

HTML:

<div class="input-group mr-2">
    <input
    [ngClass]="ngControl?.valid ? 'ng-valid' : 'ng-invalid'"
    class="form-control"
    (blur)="inputBlur($event)"
    [ngModel]="dateString | date:inputDatetimeFormat"
    (change)="onInputChange($event)"
    [disabled]="disabled"
  />

    <div class="input-group-append">
        <button
      class="btn btn-outline-secondary"
      [ngbPopover]="calendarContent"
      [disabled]="disabled"
      type="button"
    >
      <fa-icon [icon]="['far', 'calendar']"></fa-icon>
    </button>
    </div>
</div>

<ng-template #calendarContent>
    <div>
        <div *ngIf="!showTimePickerToggle">
            <ngb-datepicker id="dp" #dp name="datepicker" [ngModel]="datetime"
                (ngModelChange)="onDateChange($event, dp)"></ngb-datepicker>
            <button
        class="btn btn-block btn-outline-secondary"
        [disabled]="!datetime?.day"
        [ngbPopover]="timePickerContent"
        type="button"
        (click)="toggleDateTimeState($event)"
      >
        <fa-icon [icon]="['far', 'clock']"></fa-icon>
      </button>
        </div>
        <div *ngIf="showTimePickerToggle">
            <button
        class="btn btn-block btn-outline-secondary"
        [ngbPopover]="calendarContent"
        type="button"
        (click)="toggleDateTimeState($event)"
      >
        <fa-icon [icon]="['far', 'calendar']"></fa-icon>
      </button>
            <div class="mt-auto">
                <ngb-timepicker #tp name="timepicker" [ngModel]="datetime" (ngModelChange)="onTimeChange($event)"
                    [seconds]="seconds" [hourStep]="hourStep" [minuteStep]="minuteStep" [secondStep]="secondStep">
                </ngb-timepicker>
            </div>
        </div>
    </div>
</ng-template>

TS:

import {
  Component,
  OnInit,
  Input,
  forwardRef,
  ViewChild,
  AfterViewInit,
  Injector
} from "@angular/core";
import {
  NgbTimeStruct,
  NgbDateStruct,
  NgbPopoverConfig,
  NgbPopover,
  NgbDatepicker
} from "@ng-bootstrap/ng-bootstrap";
import {
  NG_VALUE_ACCESSOR,
  ControlValueAccessor,
  NgControl
} from "@angular/forms";
import { DatePipe } from "@angular/common";
import { DateTimeModel } from "./date-time.model";
import { noop } from "rxjs";

@Component({
  selector: "app-date-time-picker",
  templateUrl: "./date-time-picker.component.html",
  styleUrls: ["./date-time-picker.component.scss"],
  providers: [
    DatePipe,
    {
      provide: NG_VALUE_ACCESSOR,
      useExisting: forwardRef(() => DateTimePickerComponent),
      multi: true
    }
  ]
})
export class DateTimePickerComponent
  implements ControlValueAccessor, OnInit, AfterViewInit {
  @Input()
  dateString: string;

  @Input()
  inputDatetimeFormat = "M/d/yyyy H:mm:ss";
  @Input()
  hourStep = 1;
  @Input()
  minuteStep = 15;
  @Input()
  secondStep = 30;
  @Input()
  seconds = true;

  @Input()
  disabled = false;

  private showTimePickerToggle = false;

  private datetime: DateTimeModel = new DateTimeModel();
  private firstTimeAssign = true;

  // @ViewChild(NgbDatepicker, { static: true })
  // private dp: NgbDatepicker;

  @ViewChild(NgbPopover, { static: true })
  private popover: NgbPopover;

  private onTouched: () => void = noop;
  private onChange: (_: any) => void = noop;

  private ngControl: NgControl;

  constructor(private config: NgbPopoverConfig, private inj: Injector) {
    config.autoClose = "outside";
    config.placement = "auto";
  }

  ngOnInit(): void {
    this.ngControl = this.inj.get(NgControl);
  }

  ngAfterViewInit(): void {
    this.popover.hidden.subscribe($event => {
      this.showTimePickerToggle = false;
    });
  }

  writeValue(newModel: string) {
    if (newModel) {
      this.datetime = Object.assign(
        this.datetime,
        DateTimeModel.fromLocalString(newModel)
      );
      this.dateString = newModel;
      this.setDateStringModel();
    } else {
      this.datetime = new DateTimeModel();
    }
  }

  registerOnChange(fn: any): void {
    this.onChange = fn;
  }

  registerOnTouched(fn: any): void {
    this.onTouched = fn;
  }

  toggleDateTimeState($event) {
    this.showTimePickerToggle = !this.showTimePickerToggle;
    $event.stopPropagation();
  }

  setDisabledState?(isDisabled: boolean): void {
    this.disabled = isDisabled;
  }

  onInputChange($event: any) {
    const value = $event.target.value;
    const dt = DateTimeModel.fromLocalString(value);

    if (dt) {
      this.datetime = dt;
      this.setDateStringModel();
    } else if (value.trim() === "") {
      this.datetime = new DateTimeModel();
      this.dateString = "";
      this.onChange(this.dateString);
    } else {
      this.onChange(value);
    }
  }

  onDateChange($event: string | NgbDateStruct, dp: NgbDatepicker) {
    const date = new DateTimeModel($event);

    if (!date) {
      this.dateString = this.dateString;
      return;
    }

    if (!this.datetime) {
      this.datetime = date;
    }

    this.datetime.year = date.year;
    this.datetime.month = date.month;
    this.datetime.day = date.day;

    const adjustedDate = new Date(this.datetime.toString());
    if (this.datetime.timeZoneOffset !== adjustedDate.getTimezoneOffset()) {
      this.datetime.timeZoneOffset = adjustedDate.getTimezoneOffset();
    }

    this.setDateStringModel();
  }

  onTimeChange(event: NgbTimeStruct) {
    this.datetime.hour = event.hour;
    this.datetime.minute = event.minute;
    this.datetime.second = event.second;

    this.setDateStringModel();
  }

  setDateStringModel() {
    this.dateString = this.datetime.toString();

    if (!this.firstTimeAssign) {
      this.onChange(this.dateString);
    } else {
      // Skip very first assignment to null done by Angular
      if (this.dateString !== null) {
        this.firstTimeAssign = false;
      }
    }
  }

  inputBlur($event) {
    this.onTouched();
  }
}
import { NgbTimeStruct, NgbDateStruct } from "@ng-bootstrap/ng-bootstrap";
import { DatePipe } from "@angular/common";

export interface NgbDateTimeStruct extends NgbDateStruct, NgbTimeStruct {}

export class DateTimeModel implements NgbDateTimeStruct {
  year: number;
  month: number;
  day: number;
  hour: number;
  minute: number;
  second: number;

  timeZoneOffset: number;

  public constructor(init?: Partial<DateTimeModel>) {
    Object.assign(this, init);
  }

  public static fromLocalString(dateString: string): DateTimeModel {
    const date = new Date(dateString);

    const isValidDate = !isNaN(date.valueOf());

    if (!dateString || !isValidDate) {
      return null;
    }

    return new DateTimeModel({
      year: date.getFullYear(),
      month: date.getMonth() + 1,
      day: date.getDate(),
      hour: date.getHours(),
      minute: date.getMinutes(),
      second: date.getSeconds(),
      timeZoneOffset: date.getTimezoneOffset()
    });
  }

  private isInteger(value: any): value is number {
    return (
      typeof value === "number" &&
      isFinite(value) &&
      Math.floor(value) === value
    );
  }

  public toString(): string {
    if (
      this.isInteger(this.year) &&
      this.isInteger(this.month) &&
      this.isInteger(this.day)
    ) {
      const year = this.year.toString().padStart(2, "0");
      const month = this.month.toString().padStart(2, "0");
      const day = this.day.toString().padStart(2, "0");

      if (!this.hour) {
        this.hour = 0;
      }
      if (!this.minute) {
        this.minute = 0;
      }
      if (!this.second) {
        this.second = 0;
      }
      if (!this.timeZoneOffset) {
        this.timeZoneOffset = new Date().getTimezoneOffset();
      }

      const hour = this.hour.toString().padStart(2, "0");
      const minute = this.minute.toString().padStart(2, "0");
      const second = this.second.toString().padStart(2, "0");

      const tzo = -this.timeZoneOffset;
      const dif = tzo >= 0 ? "+" : "-",
        pad = function(num) {
          const norm = Math.floor(Math.abs(num));
          return (norm < 10 ? "0" : "") + norm;
        };

      const isoString = `${pad(year)}-${pad(month)}-${pad(day)}T${pad(
        hour
      )}:${pad(minute)}:${pad(second)}${dif}${pad(tzo / 60)}:${pad(tzo % 60)}`;
      return isoString;
    }

    return null;
  }
}

这是一个 link 的工作示例 https://stackblitz.com/edit/angular-datetimepicker