themesberg / flowbite

Open-source UI component library and front-end development framework based on Tailwind CSS
https://flowbite.com
MIT License
7.29k stars 708 forks source link

Dropdown Component Not Initializing correctly on Navigation with Angular RouterLink #861

Open ayoubkhial opened 2 months ago

ayoubkhial commented 2 months ago

Describe the bug When navigating between pages using Angular's routerLink, the dropdown components powered by Flowbite do not initialize correctly on the new page unless a full browser reload is performed. This issue affects navigation from a login page to an admin page, both of which utilize Flowbite's dropdown component.

To Reproduce

  1. Start the Angular application with Flowbite dropdown components on the login and admin pages.
  2. Navigate from the login page to the admin page using routerLink.
  3. Observe that the dropdown on the admin page does not function. Reload the browser on the admin page; the dropdown now works as expected.

Expected behavior The dropdown components should initialize and function correctly after navigation between pages without requiring a browser reload.

Desktop (please complete the following information):

Additional context Currently, I am initializing Flowbite on every NavigationEndevent by subscribing to router events in the ngOnInit lifecycle hook of the app.component.ts.

ngOnInit() {
  this.router.events.subscribe((event) => {
    if (event instanceof NavigationEnd) {
      initFlowbite();
    }
  });
}

Alternatively, a directive is used specifically on components where Flowbite is utilized to manage the initialization, ensuring that elements are properly recognized and initialized by Flowbite post-navigation. (source)

import { initFlowbite } from 'flowbite';
import { Subject, concatMap, delay, of } from 'rxjs';

const flowbiteQueue = new Subject<() => void>();

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 = Array.from(document.querySelectorAll('*'));
    const flowbiteElements: Element[] = [];
    const initializedElements = Array.from(document.querySelectorAll('[flowbite-initialized]'));

    for (const element of elements) {
      const attributes = Array.from(element.attributes);

      for (const attribute of attributes) {
        if (attribute.name.startsWith('data-') && !initializedElements.includes(element)) {
          flowbiteElements.push(element);
          break;
        }
      }
    }

    for (const element of flowbiteElements) {
      element.setAttribute('flowbite-initialized', '');
    }
    initFlowbite();

    for (const element of flowbiteElements) {
      const attributes: { name: string; value: string }[] = Array.from(element.attributes);
      const dataAttributes = attributes.filter((attribute) => attribute.name.startsWith('data-'));

      for (const attribute of dataAttributes) {
        element.setAttribute(attribute.name.replace('data-', 'fb-'), attribute.value);
        element.removeAttribute(attribute.name);
      }
    }
  });
}