Migrate from PrimeNG
PrimeNG and OGE cover the same ground, so most screens move by renaming elements and inputs. What needs care is smaller: a handful of defaults point the other way, menus use another item model, and confirm and toast calls return promises instead of taking callbacks. This page lists all three and the adapters that keep your existing models.
Codemod
Every OGE package ships a migrate-primeng schematic. It rewrites p-button, p-dialog, p-tree and pTooltip in templates (files and inline template: strings), swaps the PrimeNG modules for the OGE standalone imports, and turns MenuItem into OgePrimeMenuItem. What it cannot translate one-to-one — a table's columns, a defaults difference, a bound style flag — it keeps and marks with TODO(oge-migrate), and the run ends with a list of those spots. Commit first and review the diff.
# from the workspace root, on a clean working tree
ng generate @oge-ui/buttons:migrate-primeng # src/ by default
ng generate @oge-ui/overlay:migrate-primeng --path=src/app/admin
# before → after
<p-button label="Save" icon="pi pi-save" [outlined]="true" (onClick)="save()" />
<oge-button text="Save" iconClass="pi pi-save" (clicked)="save()" stylingMode="outlined" />
<p-dialog header="Edit" [(visible)]="open" [modal]="true">…</p-dialog>
<!-- TODO(oge-migrate): p-dialog relied on PrimeNG defaults (draggable, resizable, dismissableMask); … -->
<oge-modal title="Edit" [(opened)]="open">…</oge-modal>Component map
Every Angular element has a React twin with the same options, so a mixed codebase moves with one vocabulary.
| PrimeNG | OGE (Angular · React) | Package |
|---|---|---|
p-table | oge-grid · <OgeGrid> | @oge-ui/grid · @oge-ui/react-grid |
p-treeTable | oge-tree-list · <OgeTreeList> | @oge-ui/tree-list |
p-tree | oge-tree-view · <OgeTreeView> | @oge-ui/navigation |
p-dialog | oge-modal · <OgeModal> | @oge-ui/overlay |
p-confirmDialog + ConfirmationService | OgeModalService.confirm() · useOgeModals().confirm() | @oge-ui/overlay |
p-toast + MessageService | OgeToastService · useOgeToasts() | @oge-ui/overlay |
p-contextMenu | [ogeContextMenu] · <OgeContextMenu> | @oge-ui/overlay |
p-menu , p-tieredMenu | oge-menu-list · <OgeMenuList> | @oge-ui/overlay |
p-menubar | oge-menubar · <OgeMenubar> | @oge-ui/navigation |
p-popover ( p-overlayPanel ) | oge-popover · <OgePopover> | @oge-ui/overlay |
pTooltip | [ogeTooltip] · <OgeTooltip> | @oge-ui/overlay |
p-button | oge-button · <OgeButton> | @oge-ui/buttons |
p-selectButton | oge-button-group · <OgeButtonGroup> | @oge-ui/buttons |
p-splitButton | oge-drop-down-button · <OgeDropDownButton> | @oge-ui/buttons |
p-select ( p-dropdown ) | oge-select-box · <OgeSelectBox> | @oge-ui/inputs |
p-multiSelect | oge-tag-box · <OgeTagBox> | @oge-ui/inputs |
p-datepicker ( p-calendar ) | oge-date-box · <OgeDateBox> | @oge-ui/inputs |
p-inputNumber | oge-number-box · <OgeNumberBox> | @oge-ui/inputs |
p-tabs ( p-tabView ) | oge-tabs · <OgeTabs> | @oge-ui/tabs |
p-breadcrumb | oge-breadcrumb · <OgeBreadcrumb> | @oge-ui/navigation |
p-paginator | oge-pagination · <OgePagination> | @oge-ui/navigation |
p-stepper | oge-stepper · <OgeStepper> | @oge-ui/navigation |
p-drawer ( p-sidebar ) | oge-drawer · <OgeDrawer> | @oge-ui/navigation |
p-fileUpload | oge-file-uploader · <OgeFileUploader> | @oge-ui/upload |
p-editor | oge-editor · <OgeEditor> | @oge-ui/editor |
p-chart | oge-chart · <OgeChart> | @oge-ui/charts |
Defaults that differ
These are the defaults a one-to-one port gets wrong without an error — check them on every screen you move.
| Behaviour | PrimeNG default | OGE default |
|---|---|---|
| Dialog is modal | modal = false | always modal ( oge-window is the non-modal one) |
| Backdrop click closes | dismissableMask = false | closeOnBackdropClick = true |
| Dialog can be dragged | draggable = true | dragEnabled = false |
| Dialog can be resized | resizable = true | resizeEnabled = false |
| Page scroll locked while open | blockScroll = false | scrollLock = true |
| Escape closes | closeOnEscape = true | closeOnEscape = true — hiding ✕ does not turn it off |
| Toast lifetime | life = 3000 ms | toastDisplayTime = 4000 ms |
| Toast position | top-right | bottom-end (logical: mirrors in RTL) |
| Tooltip side | tooltipPosition = right | tooltipPlacement = top |
| Tooltip delay | none | tooltipShowDelayMs = 400 ms |
| Text column filter | startsWith | contains — accent- and case-insensitive, Turkish İ / ı included |
Menus
OGE menus take OgeMenuItem. ogeFromPrimeMenuItems() converts your PrimeNG MenuItem arrays as they are, submenus included: label → text, command → action, an icon class → iconClass, routerLink (with queryParams and fragment) → url, styleClass → cssClass, title / tooltip → hint; separator, disabled, visible, badge, shortcut and items keep their names. A router link becomes a real href (middle-click opens a tab); hand it to the router in the item-click handler.
import { Component, computed, input } from '@angular/core';
import { Router } from '@angular/router';
import {
OgeContextMenu,
ogeFromPrimeMenuItems,
type OgeMenuListItemClickEvent,
type OgePrimeMenuItem, // your PrimeNG MenuItem[] fits this shape as is
} from '@oge-ui/overlay';
@Component({
selector: 'app-row-actions',
imports: [OgeContextMenu],
template: `<div [ogeContextMenu]="items()" (contextMenuItemClick)="go($event)">…</div>`,
})
export class RowActions {
// the PrimeNG model you already have, unchanged
readonly model = input.required<OgePrimeMenuItem[]>();
// label → text, command → action, icon → iconClass, routerLink → url,
// styleClass → cssClass, title / tooltip → hint; separators and submenus stay
protected readonly items = computed(() => ogeFromPrimeMenuItems(this.model()));
constructor(private readonly router: Router) {}
// a routerLink became a real href: let the router take it
protected go(event: OgeMenuListItemClickEvent): void {
const url = event.item.url;
if (url?.startsWith('/')) {
event.event.preventDefault();
void this.router.navigateByUrl(url);
}
}
}import {
OgeContextMenu,
ogeFromPrimeMenuItems,
type OgePrimeMenuItem, // your PrimeNG MenuItem[] fits this shape as is
} from '@oge-ui/react-overlay';
declare const model: OgePrimeMenuItem[];
const items = ogeFromPrimeMenuItems(model);
<OgeContextMenu items={items}>
<div tabIndex={0}>Right-click me</div>
</OgeContextMenu>;Buttons
| PrimeNG | OGE |
|---|---|
label | text |
icon (a CSS class) | iconClass — or project an SVG with [ogeButtonIcon] |
severity : secondary · success · info · warn · danger · help · contrast | severity : normal · success · accent · warning · danger ( color for anything else) |
outlined · text · link | stylingMode: 'outlined' | 'text' | 'link' |
rounded | shape: 'pill' |
fluid | fluid |
loading · loadingIcon | loading · loadingText |
badge | badge |
(onClick) | (clicked) — passes the click guard, hold-to-confirm and single-flight action |
<!-- PrimeNG -->
<p-button label="Save" icon="pi pi-save" severity="secondary" [outlined]="true"
[rounded]="true" [loading]="saving" (onClick)="save()" />
<!-- OGE: the label is text, an icon class is iconClass, styles are one input -->
<oge-button text="Save" iconClass="pi pi-save" stylingMode="outlined"
shape="pill" [loading]="saving" (clicked)="save()" />Dialogs, confirm and toasts
A dialog that relied on PrimeNG's defaults needs its options spelled out — or dropped, since OGE's are the usual modal ones:
<!-- PrimeNG: non-modal, draggable and resizable unless told otherwise -->
<p-dialog header="Edit" [(visible)]="open" [modal]="true" [dismissableMask]="true">…</p-dialog>
<!-- OGE: modal, fixed, closes on a backdrop click — opt in to the rest -->
<oge-modal title="Edit" [(opened)]="open">…</oge-modal>
<oge-modal title="Edit" [(opened)]="open"
[closeOnBackdropClick]="false" [dragEnabled]="true" [resizeEnabled]="true">…</oge-modal> Confirm and toast services need no host element in the template. confirm() resolves to a boolean, and it adds gates for irreversible actions that PrimeNG leaves to you (details, typeToConfirm, acknowledgeLabel). Toast severities are info, success, warning and error:
// PrimeNG: ConfirmationService with accept / reject callbacks
this.confirmation.confirm({
header: 'Delete file?',
message: 'report.xlsx will be removed permanently.',
accept: () => this.remove(),
});
// OGE: a promise — true for the primary button, false otherwise
const ok = await this.modals.confirm({
title: 'Delete file?',
message: 'report.xlsx will be removed permanently.',
severity: 'danger',
});
if (ok) this.remove();// PrimeNG: MessageService + <p-toast>; severity 'warn', life in ms
this.messages.add({ severity: 'warn', summary: 'Quota', detail: '90% used', life: 3000 });
// OGE: OgeToastService (no host element to place); severity 'warning'
this.toasts.warning('90% used', { title: 'Quota', displayTime: 3000 });Trees
OGE trees read your own objects instead of a TreeNode wrapper: name the key, label and children fields (or pass flat rows with a parent id). Drops report through itemReordered and never mutate your data; itemDragStarting, itemDragOver and allowDropBetween replace PrimeNG's draggableNodes / droppableNodes checks.
// PrimeNG: TreeNode[] with label / children / key
const nodes: TreeNode[] = [{ key: '1', label: 'Documents', children: [/* … */] }];
// OGE: your own objects — say which fields to read
<oge-tree-view [items]="nodes" keyExpr="key" displayExpr="label" itemsExpr="children"
[allowDragging]="true" (itemReordered)="move($event)" />Dark mode
PrimeNG's darkModeSelector has two OGE forms: the prebuilt dark-class.css for .dark and [data-theme="dark"], and the Sass theme() mixin for any other selector. See Styling for the tokens and the bridge themes.
/* PrimeNG: providePrimeNG({ theme: { options: { darkModeSelector: '.app-dark' } } }) */
/* OGE, Tailwind-style .dark or [data-theme="dark"]: one import */
@import '@oge-ui/core/themes/dark-class.css';
/* OGE, any other selector (.app-dark here): the Sass mixin */
@use '@oge-ui/core/themes/dark.scss' as oge-dark;
@include oge-dark.theme('.app-dark');Moving one screen at a time
- Both libraries can live in one app while you move: OGE styles target only
.oge-*classes and declare only--oge-*tokens, so they never restyle a PrimeNG element. - Start with the screens whose grids carry the most logic — server filtering, grouping, export — since that is where the APIs differ most; the filter contract describes what the grid sends to your server.
- Keep your
MenuItemmodels and convert them at the edge withogeFromPrimeMenuItems()until the last PrimeNG menu is gone.