Нет ли средства выбора DateTime для 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>

Это то, что у меня есть до сих пор

DateTimePicker

Теперь, как отобразить выбранную дату и время в текстовом поле createdStartDate?


person Nehal Jaisalmeria    schedule 30.08.2020    source источник
comment
Читая заголовок этого вопроса, см. ссылку: danielykpan.github.io/date-time-picker   -  person Luuk    schedule 30.08.2020


Ответы (3)


Если вы хотите, вы можете использовать комбинацию с ngbDropDown, ngbDatePicker и ngbTimePicker.

Для этого вам нужны две переменные и один геттер

  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.

ПРИМЕЧАНИЕ. Это тот случай, когда мы создадим настраиваемый элемент управления формы, чтобы не создавать такую ​​​​зависимость.

Обновление для любопытства, в stackblitz Я создаю пользовательский элемент управления формой

person Eliseo    schedule 30.08.2020
comment
Большое спасибо за ваш ответ. Кроме того, как я могу использовать (change) вместо (ngModelChange)? А для чего [ngModelOptions]="{standalone:true}"? - person Nehal Jaisalmeria; 30.08.2020
comment
[ngModelOptions]="{standalone:true}" необходим, если вы используете [(ngModel)] внутри <form [formGroup]="form"> и указываете Angular, что переменные не связаны с FormGroup -else Angular дает вам ошибку - я не помню, если в Angular 7, конечно, в Angular 8 и следующий-. У ngbDatePicker или ngbTimePicker нет события (change), поэтому вы должны использовать или (dateSelect) или (ngModelChange), чтобы узнать, когда происходит изменение. - person Eliseo; 30.08.2020
comment
Я получаю эту ошибку: ERROR Error: No value accessor for form control with name: 'control' Я использую FormBuilder. Пожалуйста помогите. @Элизео - person Nehal Jaisalmeria; 01.09.2020
comment
см. строку: this.form.get("control").setValue(this._value); измените control на название вашего объекта. В любом случае, вы также можете использовать настраиваемый элемент управления формой, указанный в последнем стеке. - person Eliseo; 01.09.2020
comment
Я внес следующие изменения: constructor(private fb: FormBuilder) { } this.managePromotionsForm = this.fb.group({ createdStartDate: [''], }) this.managePromotionsForm.get("createdStartDate").setValue(this._value); - person Nehal Jaisalmeria; 01.09.2020
comment
Где я должен добавить formControlName="createdStartDate" в шаблон/HTML? @Элизео - person Nehal Jaisalmeria; 01.09.2020
comment
Если вы используете настраиваемый элемент управления формой, ответ будет положительным, иначе ответ будет отрицательным: группа форм существует сама по себе, и нет необходимости, чтобы элементы управления имели ввод. чтобы избежать ошибки инициализации, вы можете добавить if if (this.managePromotionsForm && this.managePromotionsForm.get('createdStartDate'){this.managePromotionsForm.get('createdStartDate').setValue(this._value);} - person Eliseo; 01.09.2020
comment
Как исправить значения по умолчанию для [hourStep] и [minuteStep] для ngb-timepicker? - person Nehal Jaisalmeria; 02.09.2020
comment
Я просто добавляю новый @Input в пользовательскую форму Control - person Eliseo; 02.09.2020

Если вы хотите создать элемент формы, к которому вы можете привязаться. Лучше создать отдельный компонент и реализовать аксессуар управляющего значения.

Затем вы можете использовать компонент как элемент формы и привязать к нему ngModal или элемент управления формой.

person Muhammad Kamran    schedule 30.08.2020

Для этого существует открытая проблема в качестве запроса функции: https://github.com/ng-bootstrap/ng-bootstrap/issues/2086

DatePicker и TimePicker можно комбинировать с некоторым пользовательским кодом. введите здесь описание изображения

Вот пример, который использует ввод текста с помощью кнопки для открытия средства выбора даты и переключения между средством выбора времени.

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;
  }
}

Вот ссылка на рабочий пример https://stackblitz.com/edit/angular-datetimepicker

person Cirem    schedule 22.12.2020
comment
Ссылка на решение приветствуется, но убедитесь, что ваш ответ полезен и без нее: добавьте контекст вокруг ссылки, чтобы другие пользователи иметь некоторое представление о том, что это такое и почему оно там, а затем процитировать наиболее релевантную часть страницы, на которую вы ссылаетесь, в случае, если целевая страница недоступна. Ответы, которые представляют собой не более чем ссылку, могут быть удалены. - person Victor VosMottor; 22.12.2020
comment
Хотя эта ссылка может ответить на вопрос, лучше включить сюда основные части ответа и предоставить ссылку для справки. Ответы, содержащие только ссылки, могут стать недействительными, если связанная страница изменится. – Из обзора - person lbragile; 23.12.2020