Update v4.2 - New components, 10+ framework guides, and quality improvements. Visit Changelog

Angular

Using Preline UI with Angular

A practical guide to wiring Preline UI into Angular without fighting Angular rendering and routing.

Angular and Preline UI work best when Angular owns rendering and Preline UI owns the DOM behavior after the view exists. The integration is small, but the timing matters: initialize after Angular has placed the markup in the document, and clean up when Angular removes it.

The current Preline UI package gives Angular apps a module-first path: preline/non-auto for explicit scans, named plugin classes for manual instances, and internal collections that can be cleaned when Angular removes routed or component-owned markup.

Start with the Angular mental model

Preline UI is not an Angular component library. It is a DOM-driven Tailwind CSS component system. Angular renders the template, then Preline UI scans the resulting DOM and attaches behavior to elements such as dropdowns, overlays, tabs, tooltips, and selects.

That is the reason the same markup can move between Angular, React, Vue, plain HTML, Astro, Laravel, Rails, and similar stacks. It also means Angular lifecycle hooks are part of the integration surface. If the DOM is not there yet, Preline UI has nothing to initialize.

Run autoInit after Angular renders the view

For markup owned by a component view, ngAfterViewInit is the straightforward place to initialize Preline UI. Use preline/non-auto so initialization stays explicit after Angular has finished rendering.

app.component.ts
                        
                          import { AfterViewInit, Component, OnDestroy } from "@angular/core";
                          import { HSStaticMethods } from "preline/non-auto";

                          @Component({
                            selector: "app-root",
                            templateUrl: "./app.component.html",
                          })
                          export class AppComponent implements AfterViewInit, OnDestroy {
                            ngAfterViewInit() {
                              HSStaticMethods.autoInit(["dropdown", "overlay"]);
                            }

                            ngOnDestroy() {
                              HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
                            }
                          }
                        
                      

autoInit is collection-aware. Calling it again does not create duplicate dropdown instances for the same node; it initializes markup that has not been initialized yet.

Reinitialize after Angular router navigation

Route changes are the common case where Angular replaces a large part of the page. Listen for NavigationEnd, defer one microtask, then run autoInit against the new route markup.

app.component.ts
                        
                          import { Component, OnDestroy, OnInit } from "@angular/core";
                          import { NavigationEnd, Router } from "@angular/router";
                          import { filter, Subscription } from "rxjs";
                          import { HSStaticMethods } from "preline/non-auto";

                          @Component({
                            selector: "app-root",
                            template: "<router-outlet />",
                          })
                          export class AppComponent implements OnInit, OnDestroy {
                            private subscription?: Subscription;

                            constructor(private router: Router) {}

                            ngOnInit() {
                              this.subscription = this.router.events
                                .pipe(filter((event) => event instanceof NavigationEnd))
                                .subscribe(() => {
                                  queueMicrotask(() => {
                                    HSStaticMethods.autoInit();
                                  });
                                });
                            }

                            ngOnDestroy() {
                              this.subscription?.unsubscribe();
                              HSStaticMethods.cleanCollection();
                            }
                          }
                        
                      

The important part is not the exact hook name; it is the order. Angular must finish putting the route view into the DOM before Preline UI scans it.

Choose imports by the level of control you need

For a full Preline UI installation, preline/non-auto is the safest Angular default. It gives you HSStaticMethods and plugin classes without depending on browser script or plugin auto-entry page-load timing.

preline.ts
                        
                          import { HSStaticMethods } from "preline/non-auto";

                          HSStaticMethods.autoInit(["dropdown", "overlay"]);
                          HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
                        
                      

If a component owns a single plugin root, importing the plugin class from preline/non-auto is more direct. It stays on the declared package surface while avoiding automatic page-load initialization.

Dropdown component
                        
                          import { HSDropdown } from "preline/non-auto";

                          HSDropdown.autoInit();
                        
                      

Single plugin packages are useful for small Angular surfaces

Preline UI plugins can also be consumed from single-plugin dependencies when those packages are available in your dependency set, for example @preline/dropdown, @preline/overlay, @preline/select, or @preline/range-slider. This is useful when an Angular feature only needs one interactive primitive.

Terminal
                        
                          npm install @preline/dropdown
                        
                      
dropdown-init.ts
                        
                          import HSDropdown from "@preline/dropdown/non-auto";

                          HSDropdown.autoInit();
                        
                      

In that single-package setup, the auto entry is import "@preline/dropdown". Use it for simple pages. In Angular components and routed apps, the /non-auto entry is easier to reason about because initialization stays aligned with Angular lifecycle hooks.

Use manual instances when a component owns the node

autoInit is good for route-level scans. Manual instances are better when an Angular component owns one specific plugin root and can destroy it directly in ngOnDestroy.

dropdown.component.ts
                        
                          import { AfterViewInit, Component, ElementRef, OnDestroy, ViewChild } from "@angular/core";
                          import { HSDropdown, type IHTMLElementFloatingUI } from "preline/non-auto";

                          @Component({
                            selector: "app-dropdown",
                            template: `
                              <div #dropdownRoot class="hs-dropdown relative inline-flex">
                                ...
                              </div>
                            `,
                          })
                          export class DropdownComponent implements AfterViewInit, OnDestroy {
                            @ViewChild("dropdownRoot", { static: true })
                            dropdownRoot!: ElementRef<HTMLElement>;

                            private dropdown?: InstanceType<typeof HSDropdown>;

                            ngAfterViewInit() {
                              this.dropdown = new HSDropdown(this.dropdownRoot.nativeElement as unknown as IHTMLElementFloatingUI);
                            }

                            ngOnDestroy() {
                              this.dropdown?.destroy();
                            }
                          }
                        
                      

Keep TypeScript declarations narrow

In strict Angular builds, keep any package declaration workaround local and narrow instead of disabling type checking for the whole app. For example, if you intentionally use a plugin-specific full-package path such as preline/plugins/dropdown-non-auto before matching declaration files ship, declare only that module.

src/preline.d.ts
                        
                          declare module "preline/plugins/dropdown-non-auto" {
                            const HSDropdown: {
                              new (el: HTMLElement, options?: Record<string, unknown>): {
                                destroy(): void;
                              };
                              autoInit(): void;
                            };

                            export default HSDropdown;
                          }
                        
                      

If your build reports a missing vanilla-calendar-pro/types declaration from the full preline/non-auto types, install the optional datepicker dependency or add a temporary declaration for that module. Keep that declaration close to your Preline integration so it is easy to remove when package types are updated.

Optional dependencies only matter for the plugins that use them

Most core plugins do not use jQuery. Dropdowns, overlays, tooltips, popovers, tabs, and similar components use plain JavaScript. Positioning behavior uses @floating-ui/dom.

jQuery is only relevant for Datatable because datatables.net depends on it. If jQuery and DataTables are not present, Datatable should not initialize. That dependency does not affect dropdowns, overlays, tabs, or tooltips.

Range Slider uses the JavaScript API from noUiSlider. Preline UI remains responsible for the Tailwind CSS markup and behavior wrapper; noUiSlider CSS classes do not need to be merged into Preline UI styling.

The practical checklist

  • Use preline/non-auto in Angular when you need explicit lifecycle control.
  • Run HSStaticMethods.autoInit() after Angular has rendered the component or route markup.
  • Use NavigationEnd plus a deferred callback for routed Angular apps.
  • Create manual plugin instances with ViewChild when a component owns one plugin root.
  • Call destroy() for manual instances and cleanCollection() when removing initialized groups of markup.
  • Install optional third-party dependencies only for the plugins that need them, such as datatables.net for Datatable or noUiSlider for Range Slider.

© 2026 Preline Labs.