Meal Planner
Usage
theme: 'ios' You can turn auto theming on and let it decide based on the platform.
Usage
themeVariant: 'light' You can turn auto dark mode on based on system settings.
Usage
clickToCreate: true,
// click to create is on dragToCreate: true,
// drag to create is on dragToMove: true,
// reordering is on dragToResize: true,
// resizing is on eventDelete: true
// deleting is on Fine-tune drag & drop with these options, which are false by default.
Usage
locale: localeEn Selecting the locale loads the localized settings and the translated UI text.
Meal Planner
Use the timeline summary mode/event listing to create a weekly meal planner. Meal types are represented as resources with a custom template. Events are the actual meals with custom properties, like calories or notes.
The add/edit form shows up in a custom popover that opens on double click for meal creation and on click for editing.
The popover header is customized with a custom template using the headerTemplate option.
By default the second dimension of the timeline (vertical axis) is reserved for resources, however it can be configured and used as "time slots" if the times are not relevant.
Demo

Code
import { CommonModule } from '@angular/common';
import { HttpClient } from '@angular/common/http';
import { Component, OnInit, ViewChild } from '@angular/core';
import { FormsModule } from '@angular/forms';
import {
formatDate,
MbscCalendarEvent,
MbscEventcalendarOptions,
MbscModule,
MbscPopup,
MbscPopupOptions,
Notifications,
setOptions ,
} from '@mobiscroll/angular';
setOptions({
locale: ,
theme: ' ',
themeVariant: ' '
});
@Component({
selector: 'app-root',
templateUrl: './app.component.html',
providers: [Notifications],
standalone: true,
imports: [CommonModule, FormsModule, MbscModule],
})
export class AppComponent implements OnInit {
constructor(
private http: HttpClient,
private notify: Notifications,
) {}
@ViewChild('popup', { static: false })
popup!: MbscPopup;
myMeals: MbscCalendarEvent[] = [];
name = '';
calories = '';
notes = '';
type = 1;
tempMeal: any;
headerPrimary = '';
headerDate = '';
types = [
{
id: 1,
name: 'Breakfast',
color: '#e20f0f',
kcal: '300 - 400 kcal',
icon: '🍳',
},
{
id: 2,
name: 'Elevenses',
color: '#157d13',
kcal: '100 - 200 kcal',
icon: '🍌',
},
{
id: 3,
name: 'Lunch',
color: '#32a6de',
kcal: '500 - 700 kcal',
icon: '🍜',
},
{
id: 4,
name: 'Dinner',
color: '#e29d1d',
kcal: '400 - 600 kcal',
icon: '🥙',
},
{
id: 5,
name: 'Snack',
color: '#68169c',
kcal: '100 - 200 kcal',
icon: '🥨',
},
];
popupAddButtons = [
'cancel',
{
handler: () => {
this.saveEvent();
},
keyCode: 'enter',
text: 'Add',
cssClass: 'mbsc-popup-button-primary',
},
];
popupEditButtons = [
'cancel',
{
handler: () => {
this.saveEvent();
},
keyCode: 'enter',
text: 'Save',
cssClass: 'mbsc-popup-button-primary',
},
];
popupButtons: any = [];
popupOptions: MbscPopupOptions = {
display: 'bottom',
contentPadding: false,
fullScreen: true,
onClose: () => {
if (!this.isEdit) {
// Refresh the list, if add popup was canceled, to remove the temporary event
this.myMeals = [...this.myMeals];
}
},
responsive: {
medium: {
display: 'center',
width: 400,
fullScreen: false,
touchUi: false,
},
},
};
isEdit = false;
calendarOptions: MbscEventcalendarOptions = {
view: {
timeline: {
type: 'week',
eventDisplay: 'fill',
},
},
dragToCreate: false,
dragToResize: false,
dragToMove: true,
clickToCreate: true,
extendDefaultEvent: () => ({
title: 'New meal',
allDay: true,
}),
onEventClick: (args: any) => {
const event = args.event;
const resource = args.resourceObj!;
this.isEdit = true;
this.tempMeal = event;
// Fill popup form with event data
this.loadPopupForm(event);
// Set popup options
this.popupButtons = this.popupEditButtons;
this.headerPrimary = resource.name;
this.headerDate = formatDate('DDDD, DD MMMM YYYY', new Date(event.start));
this.type = +event.resource;
// Open the popup
this.popup.open();
},
onEventCreated: (args: any) => {
const event = args.event;
setTimeout(() => {
this.isEdit = false;
this.tempMeal = event;
// Fill popup form with event data
this.loadPopupForm(event);
// Set popup options
this.popupButtons = this.popupAddButtons;
this.headerPrimary = 'New meal';
this.headerDate = formatDate('DDDD, DD MMMM YYYY', new Date(this.tempMeal.start));
this.type = +event.resource;
// Open the popup
this.popup.open();
});
},
onEventDeleted: (args) => {
setTimeout(() => {
this.deleteMeal(args.event);
});
},
};
ngOnInit(): void {
this.http.jsonp<MbscCalendarEvent[]>('https://trial.mobiscroll.com/meal-planner/', 'callback').subscribe((resp) => {
this.myMeals = resp;
});
}
loadPopupForm(event: any): void {
this.name = event.title;
this.calories = event.calories;
this.notes = event.notes;
}
saveEvent(): void {
this.tempMeal.title = this.name;
this.tempMeal.calories = this.calories;
this.tempMeal.notes = this.notes;
this.tempMeal.allDay = true;
this.tempMeal.resource = this.type;
if (this.isEdit) {
// Update the event in the list
this.myMeals = [...this.myMeals];
} else {
// Add the new event to the list
this.myMeals = [...this.myMeals, this.tempMeal];
}
// Close the popup
this.popup.close();
}
deleteMeal(event: MbscCalendarEvent): void {
this.myMeals = this.myMeals.filter((item) => item.id !== event.id);
this.notify.snackbar({
button: {
action: () => {
this.myMeals = [...this.myMeals, event];
},
text: 'Undo',
},
message: 'Meal deleted',
});
}
onDeleteClick(): void {
this.deleteMeal(this.tempMeal);
this.popup.close();
}
}
<mbsc-eventcalendar
[data]="myMeals"
[resources]="types"
[options]="calendarOptions"
[resourceTemplate]="resourceTemp"
[timelineEventContentTemplate]="eventTemp"
class="md-meal-planner-calendar"
>
<ng-template #resourceTemp let-resource>
<div class="md-meal-planner-cont">
<div class="md-meal-planner-title" [ngStyle]="{color: resource.color}">
<span class="md-meal-planner-icon">{{resource.icon}}</span>
{{resource.name}}
</div>
<div class="md-meal-planner-kcal">{{resource.kcal}}</div>
</div>
</ng-template>
<ng-template #eventTemp let-args>
<div class="md-meal-planner-event">
<div class="md-meal-planner-event-title">{{args.original.title}}</div>
<div *ngIf="args.original.calories" class="md-meal-planner-event-desc">Calories {{args.original.calories}} kcal</div>
</div>
</ng-template>
</mbsc-eventcalendar>
<mbsc-popup [options]="popupOptions" [buttons]="popupButtons" [headerTemplate]="headerTemp" #popup class="md-meal-planner-popup">
<mbsc-segmented-group [(ngModel)]="type">
<mbsc-segmented *ngFor="let t of types" [value]="t.id">{{t.name}}</mbsc-segmented>
</mbsc-segmented-group>
<div class="mbsc-form-group">
<mbsc-input label="Name" [(ngModel)]="name"></mbsc-input>
<mbsc-input label="Calories" [(ngModel)]="calories"></mbsc-input>
<mbsc-textarea label="Notes" [(ngModel)]="notes"></mbsc-textarea>
</div>
<div *ngIf="isEdit" class="mbsc-button-group">
<mbsc-button class="mbsc-button-block" color="danger" variant="outline" (click)="onDeleteClick()">Delete meal </mbsc-button>
</div>
</mbsc-popup>
<ng-template #headerTemp>
<div class="md-meal-planner-header-primary">{{ headerPrimary }}</div>
<div class="md-meal-planner-header-date">{{ headerDate }}</div>
</ng-template>
.md-meal-planner-header-primary {
font-size: 16px;
line-height: 20px;
}
.md-meal-planner-header-date {
font-size: 14px;
font-weight: 600;
line-height: 20px;
opacity: 0.6;
}
.md-meal-planner-cont {
position: relative;
padding-left: 34px;
text-align: left;
}
.md-meal-planner-title {
font-size: 20px;
}
.md-meal-planner-kcal {
color: #929292;
}
.md-meal-planner-icon {
position: absolute;
left: 0;
}
.md-meal-planner-calendar.mbsc-calendar .mbsc-schedule-event-all-day-inner {
height: 40px;
display: flex;
align-items: center;
}
.md-meal-planner-calendar .md-meal-planner-event-title {
font-size: 12px;
line-height: 18px;
}
.md-meal-planner-calendar .md-meal-planner-event-desc {
opacity: 0.6;
line-height: 18px;
}
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:first-child .mbsc-segmented-selectbox-inner {
background: #f08786;
}
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(2) .mbsc-segmented-selectbox-inner {
background: #8abe89;
}
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(3) .mbsc-segmented-selectbox-inner {
background: #99d3ef;
}
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:nth-child(4) .mbsc-segmented-selectbox-inner {
background: #f0ce8e;
}
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected .mbsc-button-bg.mbsc-material,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-selected.mbsc-windows,
.md-meal-planner-popup .mbsc-segmented-item:last-child .mbsc-segmented-selectbox-inner {
background: #b48bce;
}