Modul-sperre (module-gate)¶
Gjenbrukbar modul-lisens-sperre for Angular-sider og -elementer. Renderer projisert innhold når tenanten/brukeren har den nødvendige modulen, og en kontaktboks («Du har ikke denne modulen, kontakt hjelp@konti.no for å aktivere den») ellers. Brukes første gang for CRM sine Tilbud/Masterordre- menypunkter (se CRM-modulen), men er generell og kan brukes av enhver modul som trenger å vise et menypunkt/en side selv om den bakenforliggende modulen ikke er aktivert for tenanten.
Feature-tabell¶
| Komponent/direktiv | Selector | Hovedfunksjon | Import |
|---|---|---|---|
| ModuleGateComponent | app-module-gate |
Content-projection-wrapper: viser innhold ELLER kontaktboks | Standalone og SharedModule |
| ModuleGateDirective | *appModuleGate |
Strukturell direktiv — skjul-eller-vis et enkelt element, uten meldingspanel | Standalone og SharedModule |
| ModuleAccessService | (ingen selector — injectable) | hasModule()/hasModule$() — samme dual-source-sjekk som ModuleGuard |
providedIn: 'root' |
Kildefiler¶
| Del | Sti |
|---|---|
| Komponent | ui/ePortal.ui/src/app/shared/components/module-gate/module-gate.component.ts |
| Direktiv | ui/ePortal.ui/src/app/shared/directives/module-gate.directive.ts |
| Service | ui/ePortal.ui/src/app/services/module-access.service.ts |
ModuleAccessService — tilgangskilden¶
Både komponenten og direktivet delegerer til ModuleAccessService.hasModule(moduleId),
som er nøyaktig den samme dual-source-sjekken ModuleGuard alltid har brukt
(ekstrahert til en injectable slik at også vanlige komponenter kan spørre "har
denne tenanten modul X", ikke bare rutevakter):
localStorageuser.userAccess[](fra innloggingensMe-respons) — treff påaccess.moduleID === moduleId && (access.new === true || access.new === 1).- Fallback: JWT-tokenets
moduleAccess-claim (array eller enkelt streng av modul-IDer).
Mangler token, eller er ingen av kildene et treff, returnerer hasModule() false
(fail-closed). moduleId kan være ett tall eller en array — array betyr "har ELLER-treff
på noen av dem" (samme oppførsel som ModuleGuards data: { moduleId: [28, 29] }).
hasModule$() er en reaktiv variant (v1: synkron øyeblikksbilde pakket i of(...)) for
maler som binder med async — ingen server-kall, ingen caching/async-refresh i v1.
Bruk — <app-module-gate> (primær)¶
<app-module-gate [moduleId]="50">
<app-subscription-offer-list></app-subscription-offer-list>
</app-module-gate>
Med tilpasset kontaktadresse og tekstnøkler:
<app-module-gate
[moduleId]="50"
contactEmail="support@example.com"
messageKey="Custom.notLicensedMessage"
titleKey="Custom.notLicensedTitle"
variant="inline">
…
</app-module-gate>
variant="panel" (standard) gir en sentrert kontaktboks som mirrorer
external-project-no-access-mønsteret (ikon, tittel, tekst, mailto:-knapp) —
egnet på side-nivå. variant="inline" gir en kompakt én-linjes variant for
verktøylinjer/lister.
Inputs¶
| Input | Type | Standard | Beskrivelse |
|---|---|---|---|
moduleId |
number \| number[] |
(påkrevd) | Modul(er) som låser opp det projiserte innholdet. Array = ELLER-treff. |
contactEmail |
string |
'hjelp@konti.no' |
Adresse i meldingen og i mailto:-lenken. |
messageKey |
string |
'ModuleGate.notLicensedMessage' |
i18n-nøkkel for brødteksten. Interpoleres med {{ email }}. |
titleKey |
string |
'ModuleGate.notLicensedTitle' |
i18n-nøkkel for tittelen. |
icon |
string |
'bi-shield-lock' |
Bootstrap-ikonklasse for panel/inline-ikonet. |
variant |
'panel' \| 'inline' |
'panel' |
panel = sentrert kort (side-nivå). inline = kompakt én-linje (verktøylinje/liste). |
Bruk — *appModuleGate (skjul-uten-melding)¶
Lettvekts-variant for å skjule/vise et enkelt element (knapp, menyrad) uten kontaktboks-panelet:
<!-- skjul knappen helt når modulen er av -->
<button *appModuleGate="50" class="btn btn-kai-planner">Masterordre</button>
<!-- eller: gi et eget fallback-template -->
<div *appModuleGate="50; else locked">…</div>
<ng-template #locked>
<app-module-gate [moduleId]="50" variant="inline"></app-module-gate>
</ng-template>
Inputs¶
| Input | Type | Beskrivelse |
|---|---|---|
appModuleGate |
number \| number[] |
Modul(er) som låser opp det innkapslede viewet. Array = ELLER-treff. |
appModuleGateElse |
TemplateRef<any> \| null |
Valgfritt template som vises i stedet når modulen ikke er lisensiert. |
Brukseksempel — CRM Tilbud/Masterordre (referanseimplementasjon)¶
CrmModuleGateLandingComponent (ui/ePortal.ui/src/app/components/crm/crm-module-gate-landing/crm-module-gate-landing.component.ts)
viser mønsteret for en "tynn landingsrute": CRM-gatet (modul 31) på selve ruten
slik at menypunktet alltid er synlig, deretter videreført til den ekte siden
når abonnementsmodulen (50) er aktiv:
<app-module-gate [moduleId]="subscriptionModuleId">
<div class="text-center py-5">
<div class="spinner-border" role="status">
<span class="visually-hidden">{{ 'General.loading' | translate }}</span>
</div>
</div>
</app-module-gate>
ngOnInit(): void {
const targetRoute = this.route.snapshot.data?.['targetRoute'];
if (targetRoute && this.moduleAccessService.hasModule(this.subscriptionModuleId)) {
this.router.navigateByUrl(targetRoute);
}
}
Ruten registreres med canActivate: [ActivateService, ModuleGuard], data: { moduleId: 31, targetRoute: '/subscription/offers' }
— CRM-gaten (31) slipper alle CRM-brukere inn på landingssiden; <app-module-gate [moduleId]="50">
avgjør om de ser innholdet (her: en kort spinner før redirect) eller kontaktboksen.
i18n¶
ModuleGate.*-nøklene finnes i BÅDE no.json og en.json (nytt topp-nivå-block):
"ModuleGate": {
"notLicensedTitle": "Modulen er ikke aktivert",
"notLicensedMessage": "Du har ikke denne modulen, kontakt {{email}} for å aktivere den.",
"contactButton": "Kontakt oss"
}
Kjente fallgruver¶
| Fallgruve | Riktig mønster |
|---|---|
| Bygge en ny modul-tilgangssjekk et annet sted i koden | Bruk ModuleAccessService.hasModule() — det er den ENESTE kilden (samme som ModuleGuard). |
Forvente at hasModule() reagerer på endringer uten ny innlogging |
v1 er synkron/øyeblikksbilde (samme begrensning som ModuleGuard). Ingen auto-refresh. |
Bruke *appModuleGate når brukeren trenger en forklaring |
Bruk <app-module-gate> (med panel eller inline-variant) i stedet — direktivet skjuler stille. |
| Hardkode kontakt-e-post i malen | Bruk [contactEmail]-input (standard hjelp@konti.no) — ikke skriv adressen i HTML. |
| Gate hele menyoppføringen på modulen selv (skjuler menypunktet for tenants uten modulen) | For "alltid synlig, vis melding hvis ikke aktivert"-mønsteret: gate RUTEN på en modul brukeren uansett har (f.eks. CRM=31), og gate INNHOLDET med <app-module-gate [moduleId]="50"> — se CRM-eksempelet over. |