Using Preline UI with Angular
A practical guide to using Preline UI JavaScript plugins in Angular projects, including autoInit, router hooks, module imports, cleanup, and optional dependencies.
View guideUpdate v4.2 - New components, 10+ framework guides, and quality improvements. Visit Changelog
A practical guide to wiring Preline UI into Ember applications without treating DOM plugins as global application state.
Ember is productive because templates, routing, and component lifetimes are explicit. Preline UI fits that model when you let Ember render first, then attach Preline UI behavior to the real browser DOM.
The important distinction is initialization timing. Preline UI plugins read DOM attributes, create instances, and keep internal collections so plugins can cooperate across plain HTML, server-rendered pages, and framework apps. In Ember, use preline/non-auto, run autoInit after route renders, and create manual instances for component roots that need precise teardown.
Preline UI is a DOM-driven Tailwind CSS component system. Ember owns rendering, route transitions, and component cleanup. Preline UI should run after Ember has placed the final markup in the document, not as a top-level side effect that guesses when the page is ready.
That is why preline/non-auto is the better default for Ember apps. It imports the plugin classes and HSStaticMethods without page-load auto-initialization, so your route or modifier decides when to scan and when to destroy.
Ember route transitions can replace large parts of the page without a full document reload. Listen to the router service and schedule autoInit after the route has changed, so Preline UI scans the new DOM instead of the outgoing one.
import { next } from "@ember/runloop";
export function initialize(appInstance) {
const router = appInstance.lookup("service:router");
const initPreline = () => {
next(async () => {
const { HSStaticMethods } = await import("preline/non-auto");
HSStaticMethods.autoInit();
});
};
router.on("routeDidChange", initPreline);
initPreline();
}
export default {
initialize,
};
If one route only uses a narrow set of plugins, use a targeted scan such as HSStaticMethods.autoInit(["dropdown", "overlay"]). That keeps route rescans explicit and avoids touching components that are not present.
A route-wide scan is convenient for static markup. A modifier is better when an Ember component owns one plugin root and can clean it up when the element leaves the DOM.
import { modifier } from "ember-modifier";
export default modifier((element) => {
let dropdown;
let destroyed = false;
import("preline/non-auto").then(({ HSDropdown }) => {
if (destroyed) return;
dropdown = new HSDropdown(element);
});
return () => {
destroyed = true;
dropdown?.destroy();
};
});
<div {{hs-dropdown}} class="hs-dropdown relative inline-flex">
<button
id="hs-user-menu"
type="button"
class="hs-dropdown-toggle py-2 px-3 inline-flex items-center gap-x-2 text-sm font-medium rounded-lg border border-stone-200 bg-white text-stone-800 shadow-2xs hover:bg-stone-50 focus:outline-hidden focus:bg-stone-50 dark:bg-neutral-900 dark:border-neutral-700 dark:text-white dark:hover:bg-neutral-800 dark:focus:bg-neutral-800"
aria-haspopup="menu"
aria-expanded="false"
aria-label="Open menu"
>
Account
</button>
<div class="hs-dropdown-menu hidden min-w-40 bg-white shadow-md rounded-lg p-1 dark:bg-neutral-800" role="menu" aria-labelledby="hs-user-menu">
<a class="flex items-center gap-x-3.5 py-2 px-3 rounded-lg text-sm text-stone-800 hover:bg-stone-100 focus:outline-hidden focus:bg-stone-100 dark:text-neutral-200 dark:hover:bg-neutral-700 dark:focus:bg-neutral-700" href="/profile">
Profile
</a>
</div>
</div>
Use preline/non-auto for app-level route rescans and named classes. It gives you one module import surface for HSStaticMethods, HSDropdown, HSOverlay, HSSelect, and the other plugin classes.
const { HSStaticMethods } = await import("preline/non-auto");
HSStaticMethods.autoInit(["dropdown", "overlay"]);
HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
Direct non-auto plugin imports are useful when a modifier or service only needs one class and does not need the full static-method surface.
import HSDropdown from "preline/plugins/dropdown-non-auto";
const dropdown = new HSDropdown(element);
Preline UI plugins can also be consumed as single dependencies, for example @preline/dropdown, @preline/overlay, @preline/select, or @preline/range-slider. This is the cleanest shape when an Ember modifier only needs one plugin.
Use this shape when those single packages are available in your dependency set. In the current monolithic package, the equivalent direct non-auto import is preline/plugins/dropdown-non-auto.
import { modifier } from "ember-modifier";
import HSDropdown from "@preline/dropdown/non-auto";
export default modifier((element) => {
const dropdown = new HSDropdown(element);
return () => {
dropdown.destroy();
};
});
Preline UI stores plugin instances in internal browser collections. That registry is what lets separate plugins find each other in plain HTML and framework apps. In Ember, it means a component that creates an instance should destroy it when Ember removes the element.
Prefer modifiers for this shape. The element is the plugin root, the modifier creates the instance, and the returned cleanup function calls destroy().
If you rely on app-level autoInit after route changes, call cleanCollection before rescanning plugin types that may have been removed by the previous route. Manual modifier instances should still use destroy() instead.
const { HSStaticMethods } = await import("preline/non-auto");
HSStaticMethods.cleanCollection(["dropdown", "overlay"]);
HSStaticMethods.autoInit(["dropdown", "overlay"]);
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. Range Slider uses the JavaScript API from noUiSlider; Preline UI does not need noUiSlider CSS to make the headless JavaScript behavior work.
If you initialize optional plugins through HSStaticMethods, make the optional library available before the first import of preline/non-auto. The static methods build their plugin map when that module is loaded. A late optional plugin can also be initialized through its direct class.
import { modifier } from "ember-modifier";
import noUiSlider from "nouislider";
globalThis.noUiSlider = noUiSlider;
export default modifier((element) => {
let rangeSlider;
let destroyed = false;
import("preline/non-auto").then(({ HSRangeSlider }) => {
if (destroyed) return;
HSRangeSlider.autoInit();
rangeSlider = HSRangeSlider.getInstance(element, true)?.element;
});
return () => {
destroyed = true;
rangeSlider?.destroy();
};
});
preline/non-auto for Ember apps so initialization follows Ember rendering.HSStaticMethods.autoInit() after route changes when markup is route-owned.destroy() in the modifier cleanup.autoInit(["dropdown", "overlay"]) when a route only needs specific plugins.@preline/dropdown/non-auto when a modifier only needs one plugin class.preline/non-auto import when using HSStaticMethods, or initialize late optional plugins with their direct classes.A practical guide to using Preline UI JavaScript plugins in Angular projects, including autoInit, router hooks, module imports, cleanup, and optional dependencies.
View guideA practical guide to using Preline UI JavaScript plugins in HTML + Vite projects, including direct plugin imports, non-auto timing, manual instances, cleanup, and optional dependencies.
View guide