The issue I'm experiencing is due to a lag in asynchronous data loading in my Angular app that uses FullCalendar. Initially, when the component loads, the calendar displays empty because it is rendered before asynchronous data, such as events or daily attendance information, is available. Then, when I navigate between months, the calendar finally displays the data, indicating that the data loading and updating is working correctly at that time, but not during the initial launch of the app.
import { RecursoFuncionarioService } from 'src/app/services/recurso-funcionario.service';
import { Response } from 'src/app/helpers/classes/response';
import { CalendarOptions } from '@fullcalendar/core';
import { FullCalendarComponent } from '@fullcalendar/angular';
import interactionPlugin from '@fullcalendar/interaction';
import dayGridPlugin from '@fullcalendar/daygrid';
import esLocale from '@fullcalendar/core/locales/es';
@Component({
selector: 'app-consulta-horario',
templateUrl: './consulta-horario.component.html',
styleUrls: ['./consulta-horario.component.scss']
})
export class ConsultaHorarioComponent implements OnInit {
calendarOptions?: CalendarOptions;
asistenciaDiaria: any;
datosAsistencia: { [fecha: string]: { entradaHora: string, salidaHora: string, entradaSede: string, salidaSede: string, horasTrabajadas: string } } = {};
diasFeriadoLegal: { [key: string]: boolean } = {};
diasPermisoAdministrativo: { [key: string]: boolean } = {};
diasLicenciaMedica: { [key: string]: boolean } = {};
datosCargados = false;
@ViewChild('fullcalendar') fullcalendar?: FullCalendarComponent;
@ViewChild('eventContent') eventContentTemplate?: TemplateRef<any>;
constructor(
private recursoFuncionarioService: RecursoFuncionarioService,
) {
this.asistenciaDiaria = [{}];
}
ngOnInit(): void {
this.cargaInicial();
}
cargaInicial(): void {
const fecha = new Date();
this.Load(fecha);
}
Load(fecha: Date) {
this.recursoFuncionarioService.AsistenciaDiariaGetItems(fecha).subscribe((response: Response) => {
this.asistenciaDiaria = response.Data;
const events = this.asistenciaDiaria.flatMap((item) => {
const dia = new Date(item.Dia);
if (item.ConFeriadoLegal) {
this.diasFeriadoLegal[dia.toISOString().split('T')[0]] = true;
}
if (item.ConPermisoAdministrativo) {
this.diasPermisoAdministrativo[dia.toISOString().split('T')[0]] = true;
}
if (item.ConLicenciaMedica) {
this.diasLicenciaMedica[dia.toISOString().split('T')[0]] = true;
}
this.datosAsistencia[dia.toISOString().split('T')[0]] = {
entradaHora: item.EntradaHora ? this.formatTime(item.EntradaHora) : '',
salidaHora: item.SalidaHora ? this.formatTime(item.SalidaHora) : '',
entradaSede: item.EntradaSede || '',
salidaSede: item.SalidaSede || '',
horasTrabajadas: item.MinutosTrabajados ? this.formatWorkTime(item.MinutosTrabajados) : ''
};
this.datosCargados = true;
this.LoadCalendar();
});
});
}
LoadCalendar(): void {
if(this.datosCargados){
this.calendarOptions = {
plugins: [
dayGridPlugin,
],
initialView: 'dayGridMonth',
firstDay: 1,
fixedWeekCount: true,
showNonCurrentDates: false,
editable: false,
weekends: true,
dayMaxEvents: true,
locale: esLocale,
eventContent: this.eventContentTemplate,
headerToolbar: {
left: 'prev,next',
center: 'title',
right: 'today'
},
datesSet: (dateInfo) => {
this.Load(dateInfo.start);
},
dayCellDidMount: (arg) => {
const fecha = arg.date.toISOString().split('T')[0];
const datosDia = this.datosAsistencia[fecha];
const esFeriadoLegal = !!this.diasFeriadoLegal[fecha];
const esPermisoAdministrativo = !!this.diasPermisoAdministrativo[fecha];
const esLicenciaMedica = !!this.diasLicenciaMedica[fecha];
const iconContainer = document.createElement('div');
iconContainer.style.position = 'absolute';
iconContainer.style.bottom = '0';
iconContainer.style.right = '0';
iconContainer.style.padding = '5px';
if (datosDia) {
const asistenciaContainer = document.createElement('div');
asistenciaContainer.style.position = 'absolute';
asistenciaContainer.style.left = '0';
asistenciaContainer.style.top = '0';
asistenciaContainer.style.padding = '5px';
asistenciaContainer.style.textAlign = 'left';
asistenciaContainer.style.fontSize = '0.80rem';
asistenciaContainer.style.fontWeight = 'bold';
if (datosDia.entradaHora && datosDia.entradaSede) {
const contenedorEntrada = document.createElement('div');
const iconEntrada = document.createElement('i');
iconEntrada.className = 'pi pi-arrow-right';
iconEntrada.style.color = 'green';
contenedorEntrada.appendChild(iconEntrada);
const textoEntrada = document.createTextNode(` ${datosDia.entradaHora} - ${datosDia.entradaSede}`);
contenedorEntrada.appendChild(textoEntrada);
asistenciaContainer.appendChild(contenedorEntrada);
}
if (datosDia.salidaHora && datosDia.salidaSede) {
const contenedorSalida = document.createElement('div');
const iconSalida = document.createElement('i');
iconSalida.className = 'pi pi-arrow-left';
iconSalida.style.color = 'red';
contenedorSalida.appendChild(iconSalida);
const textoSalida = document.createTextNode(` ${datosDia.salidaHora} - ${datosDia.salidaSede}`);
contenedorSalida.appendChild(textoSalida);
asistenciaContainer.appendChild(contenedorSalida);
}
if (datosDia.horasTrabajadas) {
const contenedorHoras = document.createElement('div');
const iconHoras = document.createElement('i');
iconHoras.className = 'pi pi-clock';
iconHoras.style.color = 'blue';
contenedorHoras.appendChild(iconHoras);
const textoHoras = document.createTextNode(` ${datosDia.horasTrabajadas} Horas Trabajadas`);
contenedorHoras.appendChild(textoHoras);
asistenciaContainer.appendChild(contenedorHoras);
}
arg.el.appendChild(asistenciaContainer);
}
if (esFeriadoLegal) {
const iconFeriado = document.createElement('i');
iconFeriado.className = 'pi pi-calendar';
iconFeriado.style.color = 'green';
iconContainer.appendChild(iconFeriado);
}
if (esPermisoAdministrativo) {
const iconPermisoAdministrativo = document.createElement('i');
iconPermisoAdministrativo.className = 'pi pi-briefcase';
iconPermisoAdministrativo.style.color = 'orange';
iconContainer.appendChild(iconPermisoAdministrativo);
}
if (esLicenciaMedica) {
const iconLicenciaMedica = document.createElement('i');
iconLicenciaMedica.className = 'pi pi-calendar-plus';
iconLicenciaMedica.style.color = 'red';
iconContainer.appendChild(iconLicenciaMedica);
}
arg.el.style.position = 'relative';
arg.el.appendChild(iconContainer);
}
};
}
}
formatTime(dateTimeString: string): string {
const date = new Date(dateTimeString);
let hours = date.getHours().toString().padStart(2, '0');
let minutes = date.getMinutes().toString().padStart(2, '0');
let seconds = date.getSeconds().toString().padStart(2, '0');
return `${hours}:${minutes}:${seconds}`;
}
formatWorkTime(MinutosTrabajados: number): string {
const horas = Math.floor(MinutosTrabajados / 60);
const minutos = MinutosTrabajados % 60;
return [
horas.toString().padStart(2, '0'),
minutos.toString().padStart(2, '0')
].join(':');
}
}
<div class="grid">
<div class="col-12">
<div class="view-titulo">Consulta horario</div>
</div>
</div>
<div class="flex justify-content-evenly flex-wrap p-3">
<div>
<i class="pi pi-arrow-right" style="color: green"></i>
<label class="pl-2">Entrada</label>
</div>
<div>
<i class="pi pi-arrow-left" style="color: red"></i>
<label class="pl-2">Salida</label>
</div>
<div>
<i class="pi pi-clock" style="color: blue"></i>
<label class="pl-2">Horas trabajadas</label>
</div>
<div>
<i class="pi pi-calendar-plus" style="color: red"></i>
<label class="pl-2">Licencia médica</label>
</div>
<div>
<i class="pi pi-calendar" style="color: green"></i>
<label class="pl-2">Feriado Legal</label>
</div>
<div>
<i class="pi pi-briefcase" style="color: orange"></i>
<label class="pl-2">Permiso Administrativo</label>
</div>
</div>
<div class="grid">
<div class="col-12">
<full-calendar class="full-calendar-custom"
*ngIf="calendarOptions"
#fullcalendar
[dayCellDidMount]="dayCellDidMount"
[options]="calendarOptions">
</full-calendar>
</div>
</div>
</div>```
You can use Angular's ResolveFn. Basically you will call your data provider function from the route. Once your async data provider function gets all of the data only then it will load the component. You will have your data ready at the onInit function without any lag.
If you use this process, you don't have to call this.recursoFuncionarioService.AsistenciaDiariaGetItems(fecha).subscribe() from the component. Instead, you will call AsistenciaDiariaGetItems from the route. You will get the response of this function in your component as soon as it loads.
Please see another answer that I wrote explaining this process
https://stackoverflow.com/a/77517484/5822844
Documentation: https://angular.io/api/router/ResolveFn