NgBootstrap: Datepicker
ng-bootstrap datepicker configuration
Formatting specific days
- https://github.com/ng-bootstrap/ng-bootstrap/issues/2391
- https://stackoverflow.com/questions/55227248/ngbootstrap-ngbdatepicker-with-custom-daytemplate
- https://ng-bootstrap.github.io/#/components/datepicker/api#DayTemplateContext
Date Selection Modal
Component .ts
import { Component, Input, OnInit } from '@angular/core';
import { NgbActiveModal, NgbDateStruct, NgbCalendar, NgbDatepickerNavigateEvent } from '@ng-bootstrap/ng-bootstrap';
/**
* Modal used to select a date.
*/
@Component({
selector: 'app-date-selection-modal',
templateUrl: './date-selection-modal.component.html',
styleUrls: ['./date-selection-modal.component.scss']
})
export class DateSelectionModalComponent implements OnInit {
/**
* The modal's title. The default is 'Date Selection'.
*/
@Input() modalTitle: string;
/**
* A message that is displayed above the calendar in the modal.
*/
@Input() instructions: string;
/**
* The initial date selected when opening the modal. If not set, the current date will be selected.
*/
@Input() startDate: { year: number; month: number; day: number; };
/**
* When true, the modal will be set up to only select a year.
*/
@Input() yearSelection: boolean;
/**
* When in year selection mode, this dictates the lowest year that can be selected.
*/
@Input() minYear: number;
/**
* When in year selection mode, this dictates the highest year that can be selected.
*/
@Input() maxYear: number;
/**
* The selected date in the modal.
*/
selectedDate: NgbDateStruct;
/**
* The month that is currently selected.
*/
selectedMonth: { year: number; month: number; day?: number; };
/**
* The minimum date that can be selected in the modal.
*/
minDate: { year: number; month: number; day?: number; };
/**
* @ignore
*/
constructor(public activeModal: NgbActiveModal, private calendar: NgbCalendar) {
this.modalTitle = 'Date Selection';
this.yearSelection = false;
}
/**
* Track some initial values when opening the modal
*/
ngOnInit() {
this.selectedDate = this.calendar.getToday();
if (this.startDate) {
this.selectedDate = this.startDate;
}
this.selectedMonth = this.selectedDate;
}
/**
* This is called when navigating using the calendar control.
*/
onDatePickerNavigate(event: NgbDatepickerNavigateEvent) {
this.selectedMonth = event.next;
}
/**
* Called in year selection mode when the previous year button is clicked.
*/
onSelectPreviousYear() {
if (this.selectedDate) {
this.selectedMonth = this.selectedDate = Object.assign({}, this.selectedDate, { year: this.selectedDate.year - 1 });
}
}
/**
* Called in year selection mode when the next year button is clicked.
*/
onSelectNextYear() {
if (this.selectedDate) {
this.selectedMonth = this.selectedDate = Object.assign({}, this.selectedDate, { year: this.selectedDate.year + 1 });
}
}
/**
* Called when the submit button is called. This passes the selected date information along with the close event.
*/
onSubmit() {
this.activeModal.close({ selectedDate: this.selectedDate, selectedMonth: this.selectedMonth, msg: 'Select Date button clicked' });
}
/**
* Called in year selection mode to convert the provided year from a string to a numeric value.
*/
parseYear(year: string): number {
return parseInt(year);
}
}
Component .html
<div class="modal-header">
<h4 class="modal-title text-nowrap" [innerHTML]="modalTitle"></h4>
<div class="modal-header-actions">
<button type="button" class="btn close" title="Close Modal" (click)="activeModal.dismiss('Cross clicked')">
<i class="fas fa-times" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="modal-body">
<p [innerHTML]="instructions" *ngIf="instructions && instructions.length"></p>
<div class="date-picker" *ngIf="!yearSelection">
<ngb-datepicker class="common-datepicker no-border"
[(ngModel)]="selectedDate"
(navigate)="onDatePickerNavigate($event)"
navigation="arrows"
firstDayOfWeek="7"
[minDate]="minDate"
[startDate]="selectedDate">
</ngb-datepicker>
</div>
<div class="year-picker" *ngIf="yearSelection && selectedMonth">
<button type="button" class="btn btn-link" title="Previous Year" (click)="onSelectPreviousYear()" [disabled]="(selectedMonth && selectedMonth.year ? parseYear(selectedMonth.year) <= minYear : true)">
<i class="fas fa-chevron-left" aria-hidden="true"></i>
</button>
<div class="year-label" aria-label="Selected Year">{{selectedMonth.year}}</div>
<button type="button" class="btn btn-link" title="Next Year" (click)="onSelectNextYear()" [disabled]="(selectedMonth && selectedMonth.year ? parseYear(selectedMonth.year) >= maxYear : true)">
<i class="fas fa-chevron-right" aria-hidden="true"></i>
</button>
</div>
</div>
<div class="modal-footer d-flex justify-content-center">
<button type="button" class="btn btn-primary" title="Select Date" (click)="onSubmit()">Select</button>
<button type="button" class="btn btn-secondary" title="Close Modal" (click)="activeModal.dismiss('Cancel clicked')">Cancel</button>
</div>
Component .scss
:host {
.date-picker {
display: flex;
justify-content: center;
}
.year-picker {
display: flex;
flex-direction: row;
padding: 0 1rem;
align-items: center;
justify-content: flex-start;
button {
flex: 0 0 auto;
font-size: 1rem;
}
.year-label {
flex: 1 1 auto;
text-align: center;
font-size: larger;
height: 2rem;
line-height: 2rem;
}
}
}
Component spec.ts
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { NO_ERRORS_SCHEMA } from '@angular/core';
import { NgbModule, NgbActiveModal } from '@ng-bootstrap/ng-bootstrap';
import { DateSelectionModalComponent } from './date-selection-modal.component';
describe('DateSelectionModalComponent', () => {
let component: DateSelectionModalComponent;
let fixture: ComponentFixture<DateSelectionModalComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [ DateSelectionModalComponent ],
imports: [ NgbModule ],
providers: [ { provide: NgbActiveModal } ],
schemas: [ NO_ERRORS_SCHEMA ]// Ignore child components so we don't have to provide dependencies.
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(DateSelectionModalComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
App styling
.common-datepicker {
&.no-border {
border: none;
font-size: 0.8rem;
.bg-light {
background-color: #fff !important;
border: none;
}
.ngb-dp-weekday {
color: $brand-gray-600;
font-weight: bold;
font-style: normal;
}
}
}