Hoe gemeenschappelijke lay-outs te hergebruiken in Angular met behulp van Router

De meeste van de web-apps waaraan ik tot nu toe heb gewerkt, had een ontwerp waarbij verschillende pagina’s gebruik maken van een gemeenschappelijke lay-out. Bijvoorbeeld lay-out die bestaat uit header, footer en sidebar, die vast zijn voor elke pagina, en de inhoud die varieert per pagina. Logisch idee is om te proberen gemeenschappelijke delen te extraheren en te hergebruiken. Gebaseerd op de Angular docs, Pluralsight cursussen en andere materialen die ik vond, kwam ik met twee mogelijke opties. Om deze opties beter uit te leggen, laten we eerst een voorbeeld project definiëren.

Example ProjectLink naar deze sectie

Laten we zeggen dat we een eenvoudige app hebben met 5 verschillende routes/pagina’s (login, registratie, dashboard, gebruikers, account instellingen) en twee lay-outs. Een lay-out met inhoud en voettekst, laten we het lay-out 1 noemen, en lay-out 2 met header, voettekst, zijbalk en inhoud. Laten we ook zeggen dat de aanmeldings- en registratiepagina’s lay-out 1 hebben, en de andere lay-outs lay-out 2.

Lay-out 1 – alleen voettekstlay-out
Lay-out 2 – hoofdlay-out

Als laatste kunnen we zeggen dat onze pagina’s afzonderlijke functies van de app zijn. Met behulp van map per feature project structuur, zal elk van onze features een aparte Angular module met bijbehorende routing module.

Optie 1Link naar deze sectie

(U kunt hier spelen met het)

Layout wordt gedefinieerd als een component in een aparte module, en gebruik het als een parent component in de routing module van elke specifieke feature.

Eerst in de hoofdcomponentsjabloon (meestal AppComponent) gebruikt u alleen <router-outlet> zoals:

<>Copy
<router-outlet></router-outlet>

Daarna definieert u FooterOnlyLayoutComponent component voor layout 1 met de volgende sjabloon:

<>Copy
<div class="content" fxFlex> <router-outlet></router-outlet></div><app-footer></app-footer>

Ten slotte, om deze layout te gebruiken voor de login route, moet de route worden gespecificeerd als:

<>Kopie
...const routes: Routes = }];@NgModule({ imports: , exports: })export class LoginRoutingModule { }

Op deze manier zal, wanneer de gebruiker naar/login navigeert, FooterOnlyLayoutComponent worden weergegeven in de “router slot” van AppComponent, terwijl LoginComponent zal worden weergegeven in de router slot van FooterOnlyLayoutComponent. Om de registratie pagina FooterOnlyLayoutComponent te laten gebruiken, definieer de route op dezelfde manier, maar geef het registratie pad en component in plaats van de login.

Voor component lay-out 2 (MainLayoutComponent) hebben we het volgende sjabloon:

<>Copy
<app-header fxLayout="column"></app-header><div fxLayout="row" fxFlex="100"> <app-sidebar fxLayout="column" fxFlex="300px"></app-sidebar> <div class="content" fxLayout="column" fxFlex> <router-outlet></router-outlet> </div></div><app-footer fxLayout="column"></app-footer>

Om deze lay-out voor de dashboardpagina te gebruiken, specificeert u in de dashboardroutingmodule de route als volgt:

<>Kopie
...const routes: Routes = }];@NgModule({ imports: , exports: })export class DashboardRoutingModule { }

Wanneer de gebruiker nu naar /dashboard navigeert, wordt MainLayoutComponent weergegeven in de “router slot” van AppComponent, terwijl DashboardComponent wordt weergegeven in de “router slot” van MainLayoutComponent. Om andere pagina’s deze layout te laten gebruiken, specificeer je hun routes op dezelfde manier in hun corresponderende routing modules.

Dat is het. Nu zijn we in staat om de layout te hergebruiken tussen meerdere modules. Login en registratie routes maken gebruik van lay-out 1 (FooterOnlyLayoutComponent), terwijl dashboard, gebruikers en account instellingen routes maken gebruik van lay-out 2 (MainLayoutComponent).

IssuesLink naar deze sectie

Probleem met deze aanpak is dat lay-out onnodig opnieuw wordt gemaakt bij elke route verandering. We kunnen dat controleren door console logs in de constructors van de layout, header, footer en sidebar component te zetten. Als je eerst naar de /dashboard pagina gaat, de console controleert, en dan naar de /users gaat, zul je zien dat de constructors twee keer worden aangeroepen.

Naast de gevolgen voor de performance brengt dit nog een laag van complexiteit met zich mee als er een bepaalde toestand is die tussen routes moet worden bewaard. Laten we zeggen dat onze header een zoek-ingang heeft en dat de gebruiker iets heeft ingetypt, wanneer hij naar een andere pagina gaat, zal de header opnieuw worden aangemaakt en de invoer worden gewist. Natuurlijk kan dit worden afgehandeld door persistente toestand naar een opslagruimte, maar dat is nog steeds onnodige complexiteit.

Optie 2 – gebruik lazy loaded modulesLink naar deze sectie

(U kunt hier spelen met het)

Definieer lay-out als een component in een aparte module met routing. Laten we die module LayoutModule noemen. Definieer alle feature modules als lazy loaded children modules binnen LayoutModule.

Opnieuw, in de root component template (AppComponent) gebruik alleen <router-outlet>. Zowel layout 1 (FooterOnlyLayoutComponent) en layout 2 (MainLayoutComponent) hebben dezelfde templates als in optie 1.

Emporteer geen feature modules in de AppModule. In plaats daarvan importeren we ze op een luie manier in de LayoutRoutingModule:

<>Kopie
…const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }

Tot slot, in de routing module van elke feature module gebruik je gewoon een leeg pad en de component. Bijvoorbeeld voor aanmelding zouden de routes zijn:

<>Copy
const routes: Routes = ;

terwijl het voor het dashboard is:

<>Copy
const routes: Routes = ;

en we zijn klaar.

Opnieuw gebruiken aanmelding en registratie FooterOnlyLayoutComponent terwijl andere routes MainLayout gebruiken. Maar deze keer vermeden we het hercreëren van layout, header, footer en sidebar bij elke route verandering. Als je de console logs opnieuw in de constructors zet zul je zien dat nu alleen layouts opnieuw worden aangemaakt als je navigeert tussen routes van verschillende layouts. Dus als je van /dashboard naar /users navigeert, wordt de lay-out niet opnieuw aangemaakt, terwijl dat wel gebeurt als je van /dashboard naar /login gaat.

IssuesLink to this section

Een kleiner probleem is dat alle lazy loaded modules en hun basispaden in LayoutRoutingModule moeten worden gedefinieerd, zodat het rommelig kan worden voor grotere projecten. Groter probleem is dat we lazy loading moeten gebruiken terwijl je dat soms misschien niet wilt. Het zou mogelijk moeten zijn om layouts op dezelfde manier te hergebruiken zonder lazy loaded modules te forceren. Ik heb geprobeerd dit te omzeilen door loadChildren als volgt te specificeren:

<>Copy
...const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }

maar dit werkt alleen als je geen AOT gebruikt, wat iets is wat we zeker willen gebruiken in productie (https://github.com/angular/angular-cli/issues/4192).

Een andere mogelijke oplossing zou zijn om alle lazy loaded modules vooraf te laden door in AppModule een voorlaadstrategie op te geven zoals:

<>Copy
RouterModule.forRoot(, { preloadingStrategy: PreloadAllModules })

maar hiermee worden modules afzonderlijk gebundeld en krijg je meerdere bestanden die door de client moeten worden opgehaald, iets wat je misschien niet wilt. Ook is dit niet geschikt als u alleen bepaalde modules wilt laden. In dat geval kun je een aangepaste preload strategie schrijven, maar je eindigt nog steeds met een bestand voor elke module.

Hoe dit is gedaan met AngularJs en UI-RouterLink naar deze sectie

(Probeer het hier uit)

Dit was veel eenvoudiger te bereiken met AngularJs en UI-Router, met behulp van named views. Daar moeten we eerst abstracte layout-status definiëren:

<>Copy
$stateRegistry.register({ name: 'layout', abstract: true, views: { '@': { templateUrl: 'layout.html', }, 'header@layout': { component: 'header' }, 'sidebar@layout': { component: 'sidebar' }, 'content@layout': { template: '' }, 'footer@layout': { component: 'footer' } }});

en dan layout.html:

<>Copy
<div class="flex-column" ui-view="header"></div><div class="flex-row flex-100"> <div class="flex-column" ui-view="sidebar"></div> <div class="flex-column flex" ui-view="content"></div></div><div class="flex-column" ui-view="footer"></app-footer>

en dan bij het definiëren van status voor de eigenlijke pagina moet je layout-status gebruiken als een ouder en specifieke named view(s) overschrijven. Dus login status zou zijn:

<>Copy
$stateRegistry.register({ parent: 'layout', name: 'login', url: '/login', views: { 'content@layout': { component: 'login', }, 'header@layout': { component: '' }, 'sidebar@layout': { template: '' } }});

terwijl dashboard status zou zijn:

<>Copy
$stateRegistry.register({ parent: 'layout', name: 'dashboard', url: '/dashboard', views: { 'content@layout': { component: 'dashboard', } }});

Om de status voor de rest van de pagina’s te definiëren volg je gewoon hetzelfde patroon.

Als dat is gebeurd, voegen we console.log toe aan de $onDestroy-haak van elke component en navigeren we tussen de pagina’s. We kunnen zien dat header, sidebar en footer niet vernietigd worden als we navigeren tussen /users en /dashboard. Zelfs als we navigeren tussen pagina met de hoofd lay-out en pagina met alleen de voettekst lay-out zullen we zien dat de voettekst wordt hergebruikt.

ConclusieLink naar deze sectie

Ondanks dat het mogelijk is om een soort van hergebruik van lay-out te bereiken met Angular router, zoals hierboven beschreven, lijken beide benaderingen een beetje “hacky” en pijnlijk. Het is veel gemakkelijker te bereiken met UI Router, waar we zelfs in staat zijn om gedeelde componenten te hergebruiken tussen verschillende lay-outs, of React’s dynamische routing.

PS als je weet een betere manier om dit te doen met Angular router, deel het in de comments 🙂

EDIT:Link naar deze sectie

Optie 3Link naar deze sectie

Dankzij Alexander Carls en Lars Gyrup Brink Nielsen, die hun ideeën gedeeld in de comments, hebben we een optie 3 die alle bovengenoemde problemen oplost. Het idee is om je te abonneren op de router events en dan op elke NavigationEnd event kun je stukjes van de layout laten zien/verbergen afhankelijk van de route. Voorbeelden:

Voorbeeld 1

Voorbeeld 2 (met lazy loading)

Discussieer met de gemeenschap