Skip to content

ui-shell

@mmm/ui-shell

@mmm/ui-shell ist eine Angular-Shell fuer Backoffice-/Portal-Anwendungen. Das Paket liefert ein wiederverwendbares Layout mit:

  • Top-Navigation
  • Header mit Branding
  • optionaler Seitenleiste
  • dynamischen Header-/Toolbar-Slots
  • Footer
  • Partner-Carousel
  • Guard fuer geschuetzte Shell-Routen

Die Fachanwendung rendert ihren eigentlichen Inhalt ueber Child-Routes im router-outlet der Shell.

Was die Shell tut

Die Shell liest eine zentrale Konfiguration vom Injection-Token MMM_SHELL_OPTIONS. Aus dieser Konfiguration werden zur Laufzeit unter anderem folgende Bereiche aufgebaut:

  • navItems: linke Seiten-Navigation
  • regions.mainNav: obere Hauptnavigation
  • regions.toolbar: rechte Toolbar in der Top-Navigation
  • regions.header: Komponentenbereich im Header
  • regions.footerLinks: Footer-Links
  • regions.partnerCarousel: Partnerlogos unter dem Content
  • branding: Logo und Alt-Text
  • loginUrl und returnUrlParam: Verhalten des Guards bei nicht angemeldeten Nutzern

Zusätzlich wertet die Shell Access-Regeln aus. Damit lassen sich einzelne Nav-Items, Region-Komponenten, Footer-Links oder Partnerlogos anhand von Auth-Status, Rollen oder Berechtigungen sichtbar machen.

Voraussetzungen im Host-Frontend

Das Host-Frontend muss Folgendes bereitstellen:

  • Angular Router
  • eine Auth-Store-Implementierung, die per DI verfuegbar ist
  • ein AuthStore-Objekt mit mindestens:
type AuthSnapshot = {
status: "unknown" | "anonymous" | "authenticated";
user?: {
permissions?: string[];
roles?: string[];
} | null;
};

Die Shell erwartet, dass der Store eine snapshot-Property und optional user bereitstellt, damit Sichtbarkeiten und Guard-Entscheidungen funktionieren.

Einbau im Angular-Frontend

1. Paket einbinden

Die Shell wird als Provider konfiguriert. Aktuell ist der einfachste Weg in einer Standalone-Applikation:

import { ApplicationConfig, importProvidersFrom } from "@angular/core";
import { provideRouter } from "@angular/router";
import { UiMmmShellModule } from "@mmm/ui-shell";
import { routes } from "./app.routes";
import { shellConfig } from "./shell.config";
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
importProvidersFrom(UiMmmShellModule.forRoot(shellConfig)),
],
};

Alternativ kann dieselbe Konfiguration auch direkt ueber den Token registriert werden:

import { ApplicationConfig } from "@angular/core";
import { provideRouter } from "@angular/router";
import {
MMM_SHELL_OPTIONS,
MmmShellGuard,
} from "@mmm/ui-shell";
import { routes } from "./app.routes";
import { shellConfig } from "./shell.config";
export const appConfig: ApplicationConfig = {
providers: [
provideRouter(routes),
{ provide: MMM_SHELL_OPTIONS, useValue: shellConfig },
MmmShellGuard,
],
};

Shell-Konfiguration

Die Konfiguration hat den Typ MmmShellOptions.

Beispiel fuer shell.config.ts

import { MmmShellOptions } from "@mmm/ui-shell";
import { UserMenuComponent } from "./shell/user-menu.component";
import { TenantSwitcherComponent } from "./shell/tenant-switcher.component";
export const shellConfig: MmmShellOptions = {
loginUrl: "/login",
returnUrlParam: "returnUrl",
branding: {
logoUrl: "/assets/brand/logo.svg",
logoAlt: "MMM Portal",
},
privacyLinks: {
label: "Cookie-Einstellungen",
target: "/datenschutz",
},
navItems: [
{
label: "Dashboard",
route: "/dashboard",
icon: "dashboard_24",
access: { when: "authenticated" },
},
{
label: "Benutzer",
route: "/users",
icon: "person_24",
access: { when: "permission", anyOf: ["users.read"] },
},
],
regions: {
mainNav: [
{ label: "Start", route: "/dashboard" },
{ label: "Berichte", route: "/reports", access: { when: "role", anyOf: ["admin"] } },
],
toolbar: [
{
id: "tenant-switcher",
component: TenantSwitcherComponent,
access: { when: "authenticated" },
},
],
header: [
{
id: "user-menu",
component: UserMenuComponent,
inputs: {
showAvatar: true,
},
access: { when: "authenticated" },
},
],
footerLinks: [
{ label: "Impressum", href: "/impressum" },
{ label: "Hilfe", href: "https://example.org/help" },
],
partnerCarousel: [
{
imageUrl: "/assets/partners/partner-a.svg",
altText: "Partner A",
url: "https://partner-a.example",
sortIndex: 10,
},
],
},
};

Bedeutung der Config-Felder

type MmmShellOptions = {
iconComponent?: Type<unknown>;
navItems?: AdminNavItem[];
loginUrl?: string;
returnUrlParam?: string;
branding?: {
logoUrl?: string;
logoAlt?: string;
};
loginTeaser?: {
src?: string;
headline?: string;
description?: string;
};
privacyLinks?: {
label?: string;
target?: string;
};
regions?: {
mainNav?: MainNavItem[];
toolbar?: RegionItem[];
header?: RegionItem[];
footerLinks?: Array<{
label: string;
href: string;
access?: AccessRule;
}>;
partnerCarousel?: Array<{
imageUrl: string;
altText?: string;
url?: string;
sortIndex?: number;
access?: AccessRule;
}>;
};
};

Zugriffregeln

Folgende Access-Regeln koennen verwendet werden:

type AccessRule =
| { when: "always" }
| { when: "authenticated" }
| { when: "anonymous" }
| { when: "permission"; anyOf: string[] }
| { when: "role"; anyOf: string[] };

Sie steuern nur die Sichtbarkeit in der Shell. Die eigentliche Routenabsicherung passiert ueber MmmShellGuard und ggf. weitere fachliche Guards im Host-Frontend.

Routing: Shell als Layout-Route

Die Shell wird als Layout-Komponente in die Routen eingehangen. Die eigentlichen Fachseiten liegen darunter als children.

Beispiel app.routes.ts

import { Routes } from "@angular/router";
import { MmmLayoutComponent, MmmShellGuard } from "@mmm/ui-shell";
import { DashboardPageComponent } from "./pages/dashboard-page.component";
import { UsersPageComponent } from "./pages/users-page.component";
import { LoginPageComponent } from "./pages/login-page.component";
export const routes: Routes = [
{
path: "",
component: MmmLayoutComponent,
canActivate: [MmmShellGuard],
children: [
{
path: "",
pathMatch: "full",
redirectTo: "dashboard",
},
{
path: "dashboard",
component: DashboardPageComponent,
},
{
path: "users",
component: UsersPageComponent,
},
],
},
{
path: "login",
component: LoginPageComponent,
data: { public: true },
},
];

Was der Guard macht

MmmShellGuard prueft beim Betreten der Shell:

  • authenticated: Zugriff erlaubt
  • anonymous: Redirect auf loginUrl
  • unknown: Zugriff wird blockiert
  • data.public === true auf dem Leaf-Route-Snapshot: Zugriff erlaubt

Beim Redirect zur Login-Seite wird die aktuelle URL als Query-Parameter mitgegeben. Standard ist returnUrl.

Beispiel:

/login?returnUrl=%2Fusers

Dynamische Regionen

toolbar und header koennen Komponenten direkt aus dem Host-Frontend rendern. Dafuer wird ngComponentOutlet verwendet.

Ein RegionItem sieht so aus:

type RegionItem = {
id: string;
component: Type<unknown>;
inputs?: Record<string, unknown>;
access?: AccessRule;
};

Damit lassen sich Shell-nahe UI-Bausteine wie Benutzer-Menues, Tenant-Switcher, Kontextaktionen oder Filterleisten einhaengen, ohne die Shell selbst aendern zu muessen.

Laufzeit-Update der Shell-Konfiguration

Die Konfiguration kann auch zur Laufzeit angepasst werden. Dafuer gibt es MmmShellRuntimeService.

import { inject, Injectable } from "@angular/core";
import { MmmShellRuntimeService } from "@mmm/ui-shell";
@Injectable({ providedIn: "root" })
export class TenantBrandingService {
private readonly shellRuntime = inject(MmmShellRuntimeService);
updateLogo(logoUrl: string, logoAlt: string) {
this.shellRuntime.setConfig({
branding: {
logoUrl,
logoAlt,
},
});
}
}

setConfig(...) merged Objekte tief, Arrays werden dabei ersetzt.

Technische Zusammenfassung

Das Paket ist kein Router-Ersatz und keine komplette Auth-Loesung. Es ist eine Layout- und Navigationsschicht fuer Angular-Anwendungen.

Die Verantwortung ist wie folgt getrennt:

  • @mmm/ui-shell: Layout, Navigation, Regionen, Sichtbarkeitsregeln, Shell-Guard
  • Host-Frontend: konkrete Seiten, konkrete Region-Komponenten, Auth-Store, Fachlogik, zusaetzliche Guards

Wenn das Host-Frontend die Shell konfiguriert und als Layout-Route verwendet, entsteht eine zentrale Rahmenanwendung, in die Fachseiten konsistent eingehangen werden koennen.

Oeffentliche API

Exportiert ueber src/public-api.ts:

  • UiMmmShellModule
  • MMM_SHELL_OPTIONS
  • MmmShellOptions
  • MmmLayoutComponent
  • MmmShellGuard
  • RegionOutletComponent
  • canAccess
  • MmmShellRuntimeService