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
- 1. lehetőségLink erre a szakaszra
- IssuesLink erre a szakaszra
- 2. lehetőség – lusta betöltésű modulok használataLink erre a szakaszra
- IssuesLink erre a szakaszra
- Hogyan ezt AngularJs és UI-Router segítségével csináltukLink erre a szakaszra
- KövetkeztetésLink erre a szakaszra
- EDIT:Link erre a részre
- 3. lehetőségLink erre a részre
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.
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:
<>Copyconst routes: Routes = ;
míg a műszerfalhoz:
<>Copyconst 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:
<>CopyRouterModule.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.