Skip to content

Instantly share code, notes, and snippets.

@OmarMtya
Last active August 25, 2024 11:40
Show Gist options
  • Save OmarMtya/9ce68c563893d1c774f11a94ea73a31c to your computer and use it in GitHub Desktop.
Save OmarMtya/9ce68c563893d1c774f11a94ea73a31c to your computer and use it in GitHub Desktop.
Flowbite decorator to fix Angular routing problem
import { initFlowbite } from "flowbite";
import { Subject, concatMap, delay, of } from "rxjs";
let flowbiteQueue = new Subject<any>();
flowbiteQueue.pipe(
concatMap(item => of(item).pipe(delay(100)))
).subscribe((x) => {
x();
})
export function Flowbite() {
return function (target: any) {
const originalOnInit = target.prototype.ngOnInit;
target.prototype.ngOnInit = function () {
if (originalOnInit) {
originalOnInit.apply(this);
}
InitFlowbiteFix();
};
};
}
export function InitFlowbiteFix () {
flowbiteQueue.next(() => {
const elements = document.querySelectorAll('*');
const flowbiteElements = [];
const initializedElements = Array.from(document.querySelectorAll('[flowbite-initialized]'));
for (let i = 0; i < elements.length; i++) {
const element = elements[i];
const attributes = element.attributes;
for (let j = 0; j < attributes.length; j++) {
const attribute = attributes[j];
if (attribute.name.startsWith('data-')) {
// add to the flowbiteElements array if it doesn't exist
if (!flowbiteElements.includes(element) && !initializedElements.find(x => x.isEqualNode(element))) {
flowbiteElements.push(element);
}
}
}
}
// add an attribute to the element to indicate that it has been initialized
for (let i = 0; i < flowbiteElements.length; i++) {
const element = flowbiteElements[i];
element.setAttribute('flowbite-initialized', '');
}
initFlowbite();
flowbiteElements.forEach(element => {
const attributes: { name: string; value: string }[] = Array.from(element.attributes);
const dataAttributes = attributes.filter(attribute => attribute.name.startsWith('data-'));
dataAttributes.forEach(attribute => {
element.setAttribute(attribute.name.replace('data-', 'fb-'), attribute.value);
element.removeAttribute(attribute.name);
});
});
});
}
@merof-code
Copy link

this hack is awesome

@OmarMtya
Copy link
Author

OmarMtya commented Jul 31, 2023

this hack is awesome

Thanks! Just as a comment, you want to execute an specific function to hide the modals/drawers. This is the function:

closeModals(){ var event = new KeyboardEvent('keydown', { key: 'Escape' }); document.body.dispatchEvent(event); }

Make sure to execute it when the user clicks on a submit button or close the modal instead of the data-modal-hide attribute, that would help a lot with the possible double backdrop when you use it with dynamic elements in HTML.

@byteAr
Copy link

byteAr commented Sep 18, 2023

este truco es increible

¡Gracias! Solo como comentario, desea ejecutar una función específica para ocultar los modales/cajones. Esta es la función:

closeModals(){ var event = new KeyboardEvent('keydown', { key: 'Escape' }); document.body.dispatchEvent(event); }

Asegúrese de ejecutarlo cuando el usuario haga clic en un botón de enviar o cerrar el modal en lugar del atributo data-modal-hide, eso ayudaría mucho con el posible doble fondo cuando lo use con elementos dinámicos en HTML.

Could you make an example of how to use that method in modals and sidebar?

@OmarMtya
Copy link
Author

este truco es increible

¡Gracias! Solo como comentario, desea ejecutar una función específica para ocultar los modales/cajones. Esta es la función:
closeModals(){ var event = new KeyboardEvent('keydown', { key: 'Escape' }); document.body.dispatchEvent(event); }
Asegúrese de ejecutarlo cuando el usuario haga clic en un botón de enviar o cerrar el modal en lugar del atributo data-modal-hide, eso ayudaría mucho con el posible doble fondo cuando lo use con elementos dinámicos en HTML.

Could you make an example of how to use that method in modals and sidebar?

En lugar de utilizar el data-modal-toggle o cualquier etiqueta de ese estilo, tus botones que ejecuten con un click esa función.

<button (click)="closeModals()>Cerrar</button>

No he utilizado el sidebar, pero me imagino que si se cierra presionando la tecla Escape, seguramente funcione.
Suerte

@juanpabloguerra16
Copy link

@OmarMtya por alguna razon no me functiona tu truco.

Estoy mostrando contenido dinamico usango ngFor y por lo tanto el modal esta adentro y estoy asignando un ID diferente a cada modal. Tambien le agregue el decorado que creaste a la clase de mi componente y aun asi no abre el modal. Tienes alguna recomendacion? Saludos

@OmarMtya
Copy link
Author

@OmarMtya por alguna razon no me functiona tu truco.

Estoy mostrando contenido dinamico usango ngFor y por lo tanto el modal esta adentro y estoy asignando un ID diferente a cada modal. Tambien le agregue el decorado que creaste a la clase de mi componente y aun asi no abre el modal. Tienes alguna recomendacion? Saludos

Usualmente lo que hago es crear componentes que van a recibir el ngfor directamente, por ejemplo, tienes tu contenido dinámico que recibe el ngfor, mételo todo a un componente aparte y aplica la directiva.

Lo que hace: es necesario ejecutar la función de iniciación cuando se crea un componente, pero como el ngfor es muy probable que no existan los elementos cuando se crea su padre, o es probable que los elementos dentro del ngFor cambien si el arreglo cambia, entonces los elementos que se bindearon ya no existen, aunque sea la misma información.

Por lo cual, todos los componentes que uso en un ngFor o que se van a cargar de manera dinámica por un request http, por ejemplo, los meto dentro de componentes individuales con el decorador

@juanpabloguerra16
Copy link

Gracias Omar. Tiene sentido. Separe el component que esta haciendo la solicitud HTTP a un componente diferente y agregue el decorador al componente padre pero aun asi no funciono. No se si es porque estoy usando la ultima version de angular y componentes independientes, entonces no tengo app.module.ts en mi aplicacion

@OmarMtya
Copy link
Author

Gracias Omar. Tiene sentido. Separe el component que esta haciendo la solicitud HTTP a un componente diferente y agregue el decorador al componente padre pero aun asi no funciono. No se si es porque estoy usando la ultima version de angular y componentes independientes, entonces no tengo app.module.ts en mi aplicacion

Debería de funcionar aunque sea la versión 17. Yo también estoy usando la versión 17 en un proyecto y utilicé el mismo decorador, aunque no estoy utilizando standalone components. Quizá están faltando los imports de flowbite? Podría ser una buena idea que compartas tu componente esqueleto y ver si puedo replicar el error en mi proyecto

@JuanSanjuanLemos
Copy link

The strategy of OmaMyta worked perfectly in my project; I was having trouble displaying dropdowns. I'm using Angular 17 with the standalone version. Initially, the solution didn't work because I was calling the directive in my AppComponent, and it was only initialized once. After realizing this mistake, I chose to use the initFlowBite provided by flowBite, and that also worked.
The structure of the component was as follows:

import { Component } from '@angular/core';
import { initFlowbite } from 'flowbite';

@Component({
  selector: 'app-navbar',
  standalone: true,
  imports: [],
  templateUrl: './navbar.component.html',
  styleUrl: './navbar.component.scss'
})
export class NavbarComponent {

  ngAfterViewInit() {
    initFlowbite();
  }
}

@DavidMillanC
Copy link

Hola, talvez alguien pudo resolver de que se muestren los modals sin tener que recargar la página, aunque no hay problemas con los que no están en un @for, el problema en cuando se lo llama por datos de una tabla...

@phoenixadi2002
Copy link

I am facing the same issue on my angular 16 and i am kind of confused how to use this hack. Currently i have this in my app.component.ts :`import {Component, OnInit} from '@angular/core';
import {initFlowbite, initModals, initPopovers, initTabs, initTooltips} from 'flowbite';

@component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.scss']
})
export class AppComponent implements OnInit{
title = 'KhetSeGhar';

constructor() {
}
ngOnInit() {
console.log('hi from app')
initFlowbite();
initModals();
initPopovers();
initTabs();
initTooltips();

}
`

@Pidual
Copy link

Pidual commented May 11, 2024

The strategy of OmaMyta worked perfectly in my project; I was having trouble displaying dropdowns. I'm using Angular 17 with the standalone version. Initially, the solution didn't work because I was calling the directive in my AppComponent, and it was only initialized once. After realizing this mistake, I chose to use the initFlowBite provided by flowBite, and that also worked. The structure of the component was as follows:

import { Component } from '@angular/core';
import { initFlowbite } from 'flowbite';

@Component({
  selector: 'app-navbar',
  standalone: true,
  imports: [],
  templateUrl: './navbar.component.html',
  styleUrl: './navbar.component.scss'
})
export class NavbarComponent {

  ngAfterViewInit() {
    initFlowbite();
  }
}

Gracias crack tambien tenia ese error pero con un Dropdown menu de flowbite le agrege el initFlowbite al componente y hice la importacion y santo remedio epico 🗣️🗣️🗣️🔥🔥🔥

@tinasche
Copy link

This is awesome and has been working well for my issues so far, thank you. Though it gave an initial error in the InitFlowbiteFix function with TypeScript, just explicitly made the flowbiteElements as any type like below and works well.

const flowbiteElements:any[] = [];

@juanpabloguerra16
Copy link

Usualmente lo que hago es crear componentes que van a recibir el ngfor directamente, por ejemplo, tienes tu contenido dinámico que recibe el ngfor, mételo todo a un componente aparte y aplica la directiva.

Lo que hace: es necesario ejecutar la función de iniciación cuando se crea un componente, pero como el ngfor es muy probable que no existan los elementos cuando se crea su padre, o es probable que los elementos dentro del ngFor cambien si el arreglo cambia, entonces los elementos que se bindearon ya no existen, aunque sea la misma información.

Por lo cual, todos los componentes que uso en un ngFor o que se van a cargar de manera dinámica por un request http, por ejemplo, los meto dentro de componentes individuales con el decorador

Debería de funcionar aunque sea la versión 17. Yo también estoy usando la versión 17 en un proyecto y utilicé el mismo decorador, aunque no estoy utilizando standalone components. Quizá están faltando los imports de flowbite? Podría ser una buena idea que compartas tu componente esqueleto y ver si puedo replicar el error en mi proyecto

Tu sugieres hacer el HTTP request adentro del componente que contiene el ngfor? O sugieres que se le pase la data al componente a traves de input data binding?

Dejame ver si puedo hacer un esqueleto con API y todo para que puedas replicarlo. Muchas gracias por tu ayuda @OmarMtya

@OmarMtya
Copy link
Author

Usualmente lo que hago es crear componentes que van a recibir el ngfor directamente, por ejemplo, tienes tu contenido dinámico que recibe el ngfor, mételo todo a un componente aparte y aplica la directiva.

Lo que hace: es necesario ejecutar la función de iniciación cuando se crea un componente, pero como el ngfor es muy probable que no existan los elementos cuando se crea su padre, o es probable que los elementos dentro del ngFor cambien si el arreglo cambia, entonces los elementos que se bindearon ya no existen, aunque sea la misma información.

Por lo cual, todos los componentes que uso en un ngFor o que se van a cargar de manera dinámica por un request http, por ejemplo, los meto dentro de componentes individuales con el decorador

Debería de funcionar aunque sea la versión 17. Yo también estoy usando la versión 17 en un proyecto y utilicé el mismo decorador, aunque no estoy utilizando standalone components. Quizá están faltando los imports de flowbite? Podría ser una buena idea que compartas tu componente esqueleto y ver si puedo replicar el error en mi proyecto

Tu sugieres hacer el HTTP request adentro del componente que contiene el ngfor? O sugieres que se le pase la data al componente a traves de input data binding?

Dejame ver si puedo hacer un esqueleto con API y todo para que puedas replicarlo. Muchas gracias por tu ayuda @OmarMtya

La mejor idea sería que hagas un solo request HTTP por fuera, que sería el contenedor, y hacer un ngFor de los elementos que quieras repetir, pero que este for loop sea un componente personalizado con el decorador en el componente. Así cada componente ejecuta la función de initFlowbiteFix y se arregla el problema cada que se redibujan los elementos.

@juanpabloguerra16
Copy link

juanpabloguerra16 commented Jun 26, 2024

La mejor idea sería que hagas un solo request HTTP por fuera, que sería el contenedor, y hacer un ngFor de los elementos que quieras repetir, pero que este for loop sea un componente personalizado con el decorador en el componente. Así cada componente ejecuta la función de initFlowbiteFix y se arregla el problema cada que se redibujan los elementos.

@OmarMtya
Aqui te dejo un proyecto de prueba para que puedas replicar el error. En este caso nisiquera estoy haciendo una llamada HTTP. Simplemente al condicionar un nodo usando ngif ya no funciona luego flowbite. Intente agregar tu decorador y tampoco. Dejame saber que consigues.

https://github.com/juanpabloguerra16/flowbite-angular-app

@juanpabloguerra16
Copy link

@OmarMtya no se si pudiste ver mi mensaje? Saludos

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment