themesberg/flowbite

Dropdown Component Not Initializing correctly on Navigation with Angular RouterLink

ayoubkhial opened this issue · 0 comments

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):

  • OS: MacOS
  • Browser: Google Chrome
  • Version: 123.0.6312.122

Additional context
Currently, I am initializing Flowbite on every NavigationEnd event 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);
      }
    }
  });
}