How to reuse common layouts in Angular using Router

A legtöbb webes alkalmazás, amin eddig dolgoztam, olyan dizájnnal rendelkezett, ahol a különböző oldalak közös elrendezést használnak. Például elrendezés, amely fejlécből, láblécből és oldalsávból áll, amelyek minden oldalon fixek, és a tartalom, amely oldalanként változik. A logikus ötlet az, hogy megpróbáljuk kivonni és újrafelhasználni a közös részeket. Az Angular docs, a Pluralsight tanfolyamok és egyéb általam talált anyagok alapján két lehetséges lehetőséggel álltam elő. Hogy jobban elmagyarázzuk ezeket a lehetőségeket, először definiáljuk a példa projektet.

Példa projektLink erre a szakaszra

Tegyük fel, hogy van egy egyszerű alkalmazásunk, amely 5 különböző útvonallal/oldallal (bejelentkezés, regisztráció, műszerfal, felhasználók, fiókbeállítások) és két elrendezéssel rendelkezik. Egy elrendezés tartalommal és lábléccel, nevezzük 1. elrendezésnek, és 2. elrendezés fejléccel, lábléccel, oldalsávval és tartalommal. Továbbá mondjuk, hogy a bejelentkezés és a regisztráció oldalai az 1-es elrendezéssel rendelkeznek, míg a többiek a 2-es elrendezéssel.

Layout 1 – csak lábléc elrendezés
Layout 2 – fő elrendezés

Ez utóbbiról elmondhatjuk, hogy az oldalaink az alkalmazás különálló funkciói. A funkció szerinti mappás projektstruktúrát használva minden egyes funkciónknak külön Angular modulja lesz a megfelelő útválasztó modullal.

1. lehetőségLink erre a szakaszra

(Itt játszhatsz vele)

A layoutot komponensként definiáljuk egy külön modulban, és szülő komponensként használjuk az egyes konkrét funkciók útválasztó moduljában.

Először a gyökérkomponens sablonban (általában AppComponent) csak a <router-outlet>-t használja, mint:

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

Majd definiálja a FooterOnlyLayoutComponent komponenst az 1. elrendezéshez a következő sablonnal:

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

Végül, hogy ezt az elrendezést használjuk a bejelentkezési útvonalhoz, az útvonalat így kell megadni:

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

Így, amikor a felhasználó a/login-re navigál, a FooterOnlyLayoutComponent a AppComponent “router slotjában”, míg a LoginComponent a FooterOnlyLayoutComponent router slotjában lesz megjelenítve. Ahhoz, hogy a regisztrációs oldal a FooterOnlyLayoutComponent-t használja, definiáljuk az útvonalat ugyanígy, miközben a bejelentkezés helyett a regisztrációs útvonalat és komponenst adjuk meg.

A 2. elrendezés komponenshez (MainLayoutComponent) a következő sablonunk van:

<>Kópia
<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>

Hogy ezt az elrendezést használjuk a műszerfal oldalhoz, a műszerfal routing modulban adjuk meg a route-ot így:

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

Most, amikor a felhasználó a /dashboard-re navigál, a MainLayoutComponent a AppComponent “router slotjában”, míg a DashboardComponent a MainLayoutComponent router slotjában lesz megjelenítve. Ahhoz, hogy más oldalak is ezt az elrendezést használják, ugyanígy adjuk meg az útvonalaikat a megfelelő útválasztó modulokban.

Ez minden. Most már képesek voltunk újra felhasználni az elrendezést több modul között. A bejelentkezés és a regisztráció útvonalai az 1-es elrendezést (FooterOnlyLayoutComponent), míg a műszerfal, a felhasználók és a fiókbeállítások útvonalai a 2-es elrendezést (MainLayoutComponent) használják.

IssuesLink erre a szakaszra

A probléma ezzel a megközelítéssel az, hogy az elrendezés minden útvonalváltáskor szükségtelenül újra létrejön. Ezt úgy tudjuk ellenőrizni, hogy konzolnaplókat teszünk az elrendezés, a fejléc, a lábléc és az oldalsáv komponens konstruktoraiba. Ha először a /dashboard oldalra megyünk, ellenőrizzük a konzolt, majd a /users oldalra megyünk, látni fogjuk, hogy a konstruktorokat kétszer hívjuk meg.

A teljesítményre gyakorolt hatásokon kívül ez egy újabb komplexitási réteget hoz, ha van olyan állapot, amelyet az útvonalak között meg kell őrizni. Tegyük fel, hogy a fejlécünkben van egy keresési bemenet és a felhasználó beírt valamit, amikor átvált egy másik oldalra, a fejléc újra létrejön és a bemenet törlődik. Természetesen ez kezelhető az állapot valamilyen tárolóba történő perzisztálásával, de ez még mindig felesleges bonyolultságot jelent.

2. lehetőség – lusta betöltésű modulok használataLink erre a szakaszra

(Itt játszhatsz vele)

Definiáld az elrendezést komponensként egy külön modulban, útválasztással. Nevezzük ezt a modult LayoutModule. Definiáljuk az összes funkciómodult lusta betöltésű gyermekmodulként a LayoutModule-en belül.

A gyökérkomponens sablonban (AppComponent) ismét csak a <router-outlet>-t használjuk. Mind az 1. elrendezés (FooterOnlyLayoutComponent), mind a 2. elrendezés (MainLayoutComponent) ugyanazokkal a sablonokkal rendelkezik, mint az 1. lehetőségnél.

Ne importáljon funkciómodulokat a AppModule-ben. Ehelyett lustán importáljuk őket a LayoutRoutingModule-ben:

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

Végül az egyes feature modulok routing moduljában csak használjuk az üres útvonalat és a komponenst. Például a bejelentkezéshez az útvonalak a következők lennének:

<>Copy
const routes: Routes = ;

míg a műszerfalhoz:

<>Copy
const routes: Routes = ;

és kész is vagyunk.

A bejelentkezés és a regisztráció a FooterOnlyLayoutComponent, míg a többi útvonal a MainLayout értéket használja. Ezúttal azonban elkerültük az elrendezés, a fejléc, a lábléc és az oldalsáv újrateremtését minden egyes útvonalváltáskor. Ha ismét konzolnaplókat teszel a konstruktorokba, látni fogod, hogy most már csak akkor jönnek létre újra az elrendezések, amikor a különböző elrendezésekből származó útvonalak között navigálsz. Tehát ha /dashboard-ről /users-re navigálsz, az elrendezés nem lesz újra létrehozva, míg ha /dashboard-ről /login-re mész, akkor igen.

IssuesLink erre a szakaszra

Kisebb probléma, hogy minden lusta betöltött modult és azok alapútvonalát a LayoutRoutingModule-ben kell definiálni, így ez nagyobb projekteknél rendetlenné válhat. Nagyobb probléma, hogy a lazy loadingot kell használnunk, miközben néha talán nem is akarjuk. Lehetővé kellene tenni az elrendezések hasonló módon történő újrafelhasználását a lusta betöltött modulok kényszerítése nélkül. Ezt úgy próbáltam megkerülni, hogy a loadChildren-t így adtam meg:

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

de ez csak akkor működik, ha nem használjuk az AOT-ot, amit mindenképpen használni szeretnénk a termelésben (https://github.com/angular/angular-cli/issues/4192).

Egy másik lehetséges megoldás lenne az összes lazy loaded modul előzetes betöltése a AppModule-ben megadott előzetes betöltési stratégia megadásával, például:

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

de ezzel a modulok külön-külön vannak csomagolva, és a végén több fájlt kell a kliensnek lekérnie, amit talán nem szeretnénk. Ez akkor sem megfelelő, ha csak bizonyos modulokat akarsz lustán betölteni. Ebben az esetben lehet, hogy egyéni előtöltési stratégiát kell írni, de a végén még mindig minden modulhoz fájl lesz.

Hogyan ezt AngularJs és UI-Router segítségével csináltukLink erre a szakaszra

(Próbáld ki itt)

Ezt sokkal könnyebb volt elérni AngularJs és UI-Router segítségével, a named views használatával. Ott először absztrakt layout állapotot kell definiálnunk:

<>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' } }});

majd 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>

és utána a tényleges oldal állapotának definiálásakor a layout állapotot kell szülőnek használni és felülírni a konkrét named view(s)-t. Tehát a bejelentkezés állapota:

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

míg a műszerfal állapota:

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

A többi oldal állapotának meghatározásához kövesse ugyanezt a mintát.

Amikor ez megtörtént, adjuk hozzá a console.log-t az egyes komponensek $onDestroy kampójához, és navigáljunk az oldalak között. Láthatjuk, hogy a fejléc, az oldalsáv és a lábléc nem semmisül meg a /users és a /dashboard közötti navigáláskor. Még akkor is, ha a fő elrendezésű oldal és a csak lábléc elrendezésű oldal között navigálunk, észrevehetjük, hogy a lábléc újrafelhasználásra kerül.

KövetkeztetésLink erre a szakaszra

Még ha az Angular routerrel a fent leírtak szerint lehetséges is valamiféle elrendezés újrafelhasználást elérni, mindkét megközelítés kissé “haknizósnak” és fájdalmasnak tűnik. Sokkal egyszerűbb ezt elérni az UI Routerrel, ahol akár a különböző elrendezések között megosztott komponenseket is újra tudjuk használni, vagy a React dinamikus útválasztásával.

PS, ha tudsz valami jobb módot arra, hogy ezt Angular routerrel kezeld, kérlek oszd meg a kommentekben 🙂

EDIT:Link erre a részre

3. lehetőségLink erre a részre

Hála Alexander Carlsnak és Lars Gyrup Brink Nielsennek, akik megosztották az ötleteiket a kommentekben, van egy 3. lehetőségünk, ami megoldja az összes fent említett problémát. Az ötlet az, hogy feliratkozunk a router eseményekre, majd minden egyes NavigationEnd eseményre az útvonaltól függően megjeleníthetjük/elrejthetjük az elrendezés darabjait. Példák:

1. példa

2. példa (lusta betöltéssel)

Megvitatjuk a közösséggel.