Jak opakovaně používat společné rozvržení v Angularu pomocí Routeru
Většina webových aplikací, na kterých jsem dosud pracoval, měla design, kde různé stránky používaly společné rozvržení. Například rozvržení, které se skládá ze záhlaví, zápatí a postranního panelu, které jsou pro každou stránku pevně dané, a obsahu, který se na stránce mění. Logickou myšlenkou je pokusit se vyjmout a znovu použít společné části. Na základě dokumentů Angularu, kurzů Pluralsight a dalších materiálů, které jsem našel, jsem přišel na dvě možné varianty. Abychom tyto možnosti lépe vysvětlili, definujme si nejprve příkladový projekt.
- Příkladový projektOdkaz na tuto část
- Možnost 1Odkaz na tuto část
- ProblémyOdkaz na tuto část
- Možnost 2 – použití líně načítaných modulůOdkaz na tuto sekci
- ProblémyOdkaz na tuto sekci
- Jak se to dělalo s AngularJs a UI-RouteremOdkaz na tuto část
- ZávěrOdkaz na tuto část
- EDIT:Odkaz na tuto sekci
- Varianta 3Odkaz na tuto sekci
Příkladový projektOdkaz na tuto část
Řekněme, že máme jednoduchou aplikaci, která má 5 různých cest/stránek (přihlášení, registrace, ovládací panel, uživatelé, nastavení účtu) a dva layouty. Jeden layout s obsahem a patičkou, říkejme mu layout 1, a layout 2 se záhlavím, patičkou, postranním panelem a obsahem. Také řekněme, že přihlašovací a registrační stránky mají layout 1, zatímco ostatní mají layout 2.
Nakonec můžeme říci, že naše stránky jsou samostatné funkce aplikace. Pomocí struktury projektu složek podle funkcí bude mít každá z našich funkcí samostatný modul Angular s odpovídajícím směrovacím modulem.
Možnost 1Odkaz na tuto část
(Můžete si s ní pohrát zde)
Rozvržení je definováno jako komponenta v samostatném modulu a používá se jako nadřazená komponenta v směrovacím modulu každé konkrétní funkce.
Nejprve v šabloně kořenové komponenty (obvykle AppComponent) použijte pouze <router-outlet>
jako:
<>Kopírovat<router-outlet></router-outlet>
Poté definujte komponentu FooterOnlyLayoutComponent
pro rozvržení 1 pomocí následující šablony:
<>Copy<div class="content" fxFlex> <router-outlet></router-outlet></div><app-footer></app-footer>
Nakonec, aby bylo možné použít toto rozložení pro přihlašovací trasu, musí být trasa specifikována takto:
<>Copy...const routes: Routes = }];@NgModule({ imports: , exports: })export class LoginRoutingModule { }
Takto, když uživatel přejde na /login
, FooterOnlyLayoutComponent
se vykreslí do „slotu směrovače“ AppComponent
, zatímco LoginComponent
se vykreslí do slotu směrovače FooterOnlyLayoutComponent
. Chcete-li, aby registrační stránka používala FooterOnlyLayoutComponent
, definujte trasu stejným způsobem, přičemž místo přihlašovacího jména zadejte registrační cestu a komponentu.
Pro komponentu layoutu 2 (MainLayoutComponent
) máme následující šablonu:
<>Kopírovat<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>
Chceme-li tento layout použít pro stránku ovládacího panelu, zadáme v modulu směrování ovládacího panelu trasu takto:
<>Copy...const routes: Routes = }];@NgModule({ imports: , exports: })export class DashboardRoutingModule { }
Nyní, když uživatel přejde na /dashboard
, MainLayoutComponent
se vykreslí do „slotu směrovače“ AppComponent
, zatímco DashboardComponent
se vykreslí do slotu směrovače MainLayoutComponent
. Chcete-li, aby toto rozložení používaly i další stránky, zadejte jejich směrování stejným způsobem v příslušných směrovacích modulech.
To je vše. Nyní jsme byli schopni opakovaně použít rozvržení mezi více moduly. Trasy pro přihlášení a registraci používají rozvržení 1 (FooterOnlyLayoutComponent
), zatímco trasy pro ovládací panel, uživatele a nastavení účtu používají rozvržení 2 (MainLayoutComponent
).
ProblémyOdkaz na tuto část
Problémem tohoto přístupu je, že rozvržení se zbytečně znovu vytváří při každé změně trasy. To můžeme zkontrolovat tak, že do konstruktorů komponent layout, header, footer a sidebar vložíme konzolové protokoly. Pokud nejprve přejdete na stránku /dashboard
, zkontrolujete konzolu a poté přejdete na /users
, uvidíte, že konstruktory jsou volány dvakrát.
Kromě důsledků pro výkon to přináší další vrstvu složitosti, pokud je třeba mezi trasami udržovat nějaký stav. Řekněme, že naše hlavička má vstup pro vyhledávání a uživatel něco zadal, když přejde na jinou stránku, hlavička se znovu vytvoří a vstup se vymaže. Samozřejmě to lze řešit perzistencí stavu do nějakého úložiště, ale to je stále zbytečná složitost.
Možnost 2 – použití líně načítaných modulůOdkaz na tuto sekci
(Můžete si s tím pohrát zde)
Definice rozložení jako komponenty v samostatném modulu se směrováním. Nazvěme tento modul LayoutModule
. Všechny moduly funkcí definujte jako líně načítané podřízené moduly uvnitř LayoutModule
.
V šabloně kořenové komponenty (AppComponent
) opět použijte pouze <router-outlet>
. Rozložení 1 (FooterOnlyLayoutComponent
) i rozložení 2 (MainLayoutComponent
) mají stejné šablony jako ve variantě 1.
Neimportujte moduly funkcí v AppModule
. Místo toho je budeme líně importovat v LayoutRoutingModule
:
<>Kopírovat…const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }
Nakonec v modulu směrování každého modulu funkce stačí použít prázdnou cestu a komponentu. Například pro přihlášení by trasy byly:
<>Copyconst routes: Routes = ;
zatímco pro dashboard je to:
<>Copyconst routes: Routes = ;
a máme hotovo.
Přihlášení a registrace používají FooterOnlyLayoutComponent
, zatímco ostatní trasy používají MainLayout
. Tentokrát jsme se však vyhnuli opětovnému vytváření rozvržení, záhlaví, zápatí a postranního panelu při každé změně trasy. Pokud do konstruktorů opět vložíte konzolové protokoly, uvidíte, že nyní se layouty znovu vytvářejí pouze při přechodu mezi trasami z různých layoutů. Takže pokud přejdete z /dashboard
na /users
, rozvržení se znovu nevytvoří, zatímco pokud přejdete z /dashboard
na /login
, vytvoří se.
ProblémyOdkaz na tuto sekci
Menší problém je, že všechny líně načítané moduly a jejich základní cesty musí být definovány v LayoutRoutingModule
, takže to může být u větších projektů nepřehledné. Větší problém je, že musíme používat líné načítání, zatímco někdy možná nechcete. Mělo by být možné opakovaně používat rozvržení podobně bez vynucování líně načítaných modulů. Zkoušel jsem to obejít zadáním loadChildren takto:
<>Copy...const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }
ale to funguje pouze v případě, že nepoužíváte AOT, což je něco, co určitě chceme používat v produkci (https://github.com/angular/angular-cli/issues/4192).
Dalším možným řešením by bylo přednačítání všech líně načítaných modulů zadáním strategie přednačítání v AppModule
jako:
<>CopyRouterModule.forRoot(, { preloadingStrategy: PreloadAllModules })
ale při tomto řešení jsou moduly svázány zvlášť a nakonec máte více souborů, které musí klient načíst, což je něco, co možná nechcete. Také to není vhodné, pokud chcete líně načítat jen některé konkrétní moduly. V takovém případě si možná budete chtít napsat vlastní strategii přednačítání, ale stejně skončíte se souborem pro každý modul.
Jak se to dělalo s AngularJs a UI-RouteremOdkaz na tuto část
(Vyzkoušejte si to zde)
Toho bylo mnohem jednodušší dosáhnout s AngularJs a UI-Routerem pomocí pojmenovaných pohledů. Tam musíme nejprve definovat abstraktní stav rozvržení:
<>Kopírovat$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' } }});
poté layout.html:
<>Kopírovat<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>
a pak při definování stavu pro skutečnou stránku musíte použít stav rozvržení jako rodiče a přepsat konkrétní pojmenované pohledy. Takže stav přihlášení by byl:
<>Copy$stateRegistry.register({ parent: 'layout', name: 'login', url: '/login', views: { 'content@layout': { component: 'login', }, 'header@layout': { component: '' }, 'sidebar@layout': { template: '' } }});
zatímco stav ovládacího panelu by byl:
<>Copy$stateRegistry.register({ parent: 'layout', name: 'dashboard', url: '/dashboard', views: { 'content@layout': { component: 'dashboard', } }});
Pro definování stavu pro ostatní stránky stačí postupovat podle stejného vzoru.
Jakmile je to hotovo, přidejme console.log
do háčku $onDestroy
každé komponenty a přejděme mezi stránkami. Vidíme, že při přecházení mezi /users
a /dashboard
nedojde ke zničení záhlaví, postranního panelu a zápatí. Dokonce i při navigaci mezi stránkou s hlavním rozvržením a stránkou s rozvržením pouze zápatí si všimneme, že zápatí je znovu použito.
ZávěrOdkaz na tuto část
Přestože je možné dosáhnout určitého druhu opakovaného použití rozvržení pomocí routeru Angular, jak je popsáno výše, oba přístupy se zdají být trochu „háklivé“ a bolestivé. Mnohem jednodušší je dosáhnout toho pomocí UI Routeru, kde jsme dokonce schopni znovu použít sdílené komponenty mezi různými rozvrženími, nebo dynamického směrování Reactu.
Pokud znáte nějaký lepší způsob, jak to řešit pomocí routeru Angular, podělte se o něj v komentářích 🙂
EDIT:Odkaz na tuto sekci
Varianta 3Odkaz na tuto sekci
Díky Alexandru Carlsovi a Larsu Gyrupu Brink Nielsenovi, kteří se podělili o své nápady v komentářích, máme možnost 3, která řeší všechny výše zmíněné problémy. Myšlenkou je přihlásit se k událostem směrovače a pak při každé události NavigationEnd zobrazit/skrýt části rozvržení v závislosti na trase. Příklady:
Příklad 1
Příklad 2 (s líným načítáním)
Diskutujte s komunitou.