How to reuse common layouts in Angular using Router
Useimmissa web-sovelluksissa, joiden parissa olen tähän mennessä työskennellyt, on ollut suunnittelu, jossa eri sivut käyttävät yhteistä ulkoasua. Esimerkiksi ulkoasu, joka koostuu otsikosta, alatunnisteesta ja sivupalkista, jotka ovat kiinteitä jokaiselle sivulle, ja sisällöstä, joka vaihtelee sivukohtaisesti. Looginen ajatus on yrittää poimia ja käyttää uudelleen yhteisiä osia. Angularin asiakirjojen, Pluralsightin kurssien ja muun löytämäni materiaalin perusteella keksin kaksi mahdollista vaihtoehtoa. Selittääksemme nuo vaihtoehdot paremmin, määritetään ensin esimerkkiprojekti.
- EsimerkkiprojektiLinkki tähän osioon
- Vaihtoehto 1Linkki tähän osioon
- KysymyksetLinkki tähän osioon
- Vaihtoehto 2 – käytä laiskasti ladattuja moduulejaLinkki tähän osioon
- IssuesLinkki tähän osioon
- Miten tämä tehtiin AngularJs:llä ja UI-RouterillaLinkki tähän osioon
- LoppupäätelmäLinkki tähän jaksoon
- EDIT:Linkki tähän osioon
- Vaihtoehto 3Linkki tähän osioon
EsimerkkiprojektiLinkki tähän osioon
Esitettäköön, että meillä on yksinkertainen sovellus, jossa on 5 erilaista reittiä/sivua (sisäänkirjautuminen, rekisteröityminen, kojelauta, käyttäjät, tiliasetukset) ja kaksi ulkoasua. Yksi ulkoasu, jossa on sisältö ja alatunniste, sanotaan sitä ulkoasuksi 1, ja ulkoasu 2, jossa on otsikko, alatunniste, sivupalkki ja sisältö. Sanotaan myös, että kirjautumis- ja rekisteröintisivuilla on layout 1, kun taas muilla on layout 2.
Viimeiseksi voimme sanoa, että sivumme ovat sovelluksen erillisiä ominaisuuksia. Käyttämällä kansiokohtaista ominaisuuskohtaista projektirakennetta, jokaisella ominaisuudellamme on erillinen Angular-moduuli ja sitä vastaava reititysmoduuli.
Vaihtoehto 1Linkki tähän osioon
(Voit leikkiä sillä täällä)
Layout määritellään komponenttina erillisessä moduulissa, ja käytämme sitä vanhempana komponenttina kunkin tietyn ominaisuuden reititysmoduulissa.
Ensin juurikomponentin mallissa (yleensä AppComponent) käytetään vain <router-outlet>
kuten:
<>Kopioi<router-outlet></router-outlet>
Määritä sitten FooterOnlyLayoutComponent
-komponentti asettelulle 1 seuraavalla mallilla:
<>Copy<div class="content" fxFlex> <router-outlet></router-outlet></div><app-footer></app-footer>
Viimeiseksi, jotta tätä asettelua voidaan käyttää sisäänkirjautumisreittiä varten, reitti on määriteltävä seuraavasti:
<>Copy...const routes: Routes = }];@NgModule({ imports: , exports: })export class LoginRoutingModule { }
Tällöin, kun käyttäjä navigoi osoitteeseen/login
, FooterOnlyLayoutComponent
renderöidään AppComponent
:n ”reititinpaikkaan”, kun taas LoginComponent
renderöidään FooterOnlyLayoutComponent
:n reititinpaikkaan. Jos haluat rekisteröintisivun käyttävän FooterOnlyLayoutComponent
, määrittele reititin samalla tavalla, mutta anna rekisteröintipolku ja -komponentti kirjautumisen sijasta.
Layout 2 -komponenttia (MainLayoutComponent
) varten meillä on seuraava malli:
<>Kopioi<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>
Käyttääksesi tätä layoutia kojelauta-sivulla, määritä kojelaudan reititysmoduulissa reitti näin:
<>Copy...const routes: Routes = }];@NgModule({ imports: , exports: })export class DashboardRoutingModule { }
Nyt kun käyttäjä navigoi /dashboard
:een, MainLayoutComponent
renderöidään AppComponent
:n ”reititinpaikkaan”, kun taas DashboardComponent
renderöidään MainLayoutComponent
:n reititinpaikkaan. Jotta muut sivut käyttäisivät tätä asettelua, määritä niiden reitit samalla tavalla niiden vastaavissa reititysmoduuleissa.
Se on siinä. Nyt pystyimme käyttämään asettelua uudelleen useiden moduulien välillä. Kirjautumis- ja rekisteröintireitit käyttävät asettelua 1 (FooterOnlyLayoutComponent
), kun taas kojelaudan, käyttäjien ja tiliasetusten reitit käyttävät asettelua 2 (MainLayoutComponent
).
KysymyksetLinkki tähän osioon
Tämän lähestymistavan ongelmana on, että asettelua luodaan tarpeettomasti uudelleen jokaisella reitinvaihdolla. Voimme tarkistaa tämän laittamalla konsolilokit layout-, header-, footer- ja sidebar-komponentin konstruktoreihin. Jos menet ensin /dashboard
-sivulle, tarkistat konsolin ja menet sitten /users
-sivulle, näet, että konstruktoreita kutsutaan kahdesti.
Suorituskykyyn liittyvien vaikutusten lisäksi tämä tuo mukanaan toisen kerroksen monimutkaisuutta, jos on olemassa jokin tila, joka on säilytettävä reittien välillä. Oletetaan, että otsikossamme on hakusyöttö ja käyttäjä kirjoitti jotain, kun hän siirtyy toiselle sivulle, otsikko luodaan uudelleen ja syöttö tyhjennetään. Tämä voidaan tietysti hoitaa persistoimalla tila johonkin tallennustilaan, mutta se on silti tarpeetonta monimutkaisuutta.
Vaihtoehto 2 – käytä laiskasti ladattuja moduulejaLinkki tähän osioon
(Voit leikkiä sillä täällä)
Määrittele ulkoasu komponenttina erillisessä moduulissa, jossa on reititys. Kutsutaan tuota moduulia LayoutModule
. Määrittele kaikki ominaisuusmoduulit laiskasti ladattaviksi lapsimoduuleiksi LayoutModule
:n sisälle.
Käytä taas juurikomponentin mallissa (AppComponent
) vain <router-outlet>
. Sekä asettelulla 1 (FooterOnlyLayoutComponent
) että asettelulla 2 (MainLayoutComponent
) on samat mallit kuin vaihtoehdossa 1.
Älä tuo ominaisuusmoduuleja AppModule
:ssä. Sen sijaan tuodaan ne laiskasti LayoutRoutingModule
:
<>Kopioi…const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }
Viimeiseksi kunkin ominaisuusmoduulin reititysmoduulissa käytetään vain tyhjää polkua ja komponenttia. Esimerkiksi kirjautumista varten reitit olisivat:
<>Kopioiconst routes: Routes = ;
kun taas kojelaudalle se on:
<>Kopioiconst routes: Routes = ;
ja olemme valmiita.
Jälleen kirjautumisessa ja ilmoittautumisessa käytetään FooterOnlyLayoutComponent
, kun taas muissa reiteissä käytetään MainLayout
. Tällä kertaa vältimme kuitenkin ulkoasun, otsikon, alatunnisteen ja sivupalkin luomisen uudelleen jokaisen reitin vaihdon yhteydessä. Jos laitat konsolilokit uudelleen konstruktoreihin, näet, että nyt asettelut luodaan uudelleen vain silloin, kun siirryt reittien välillä eri asetteluista. Joten jos siirryt reitiltä /dashboard
reitille /users
asettelua ei luoda uudelleen, kun taas jos siirryt reitiltä /dashboard
reitille /login
se luodaan uudelleen.
IssuesLinkki tähän osioon
Pienempi ongelma on se, että kaikki laiskasti ladatut moduulit ja niiden peruspolut on määriteltävä LayoutRoutingModule
:ssä, joten siitä voi tulla sotkuista isommissa projekteissa. Suurempi ongelma on se, että joudumme käyttämään lazy loadingia, vaikka joskus ehkä et halua sitä. Ulkoasuja pitäisi pystyä käyttämään uudelleen samalla tavalla pakottamatta lazy loaded -moduuleja. Yritin kiertää tämän määrittelemällä loadChildrenin näin:
<>Copy...const routes: Routes = }, { path: '', component: FooterOnlyLayoutComponent, children: },];@NgModule({ imports: , exports: })export class LayoutRoutingModule { }
mutta tämä toimii vain, jos ei käytetä AOT:tä, jota haluamme ehdottomasti käyttää tuotannossa (https://github.com/angular/angular-cli/issues/4192).
Toinen mahdollinen ratkaisu olisi esiladata kaikki laiskasti ladatut moduulit määrittelemällä esilatausstrategia kohdassa AppModule
kuten:
<>CopyRouterModule.forRoot(, { preloadingStrategy: PreloadAllModules })
mutta tällä moduulit niputetaan erikseen ja päädyt useisiin tiedostoihin, jotka asiakkaan täytyy hakea, mitä et ehkä halua. Tämä ei myöskään ole tarkoituksenmukaista, jos haluat ladata vain joitakin tiettyjä moduuleja. Siinä tapauksessa saatat haluta kirjoittaa mukautetun esilatausstrategian, mutta sinulle jää silti tiedosto jokaista moduulia varten.
Miten tämä tehtiin AngularJs:llä ja UI-RouterillaLinkki tähän osioon
(Kokeile sitä täältä)
Tämä oli paljon helpompi toteuttaa AngularJs:llä ja UI-Routerilla käyttämällä nimettyjä näkymiä. Siellä täytyy ensin määritellä abstrakti layout-tila:
<>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' } }});
Sitten 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>
ja sitten kun määritellään varsinaista sivua varten tila, täytyy käyttää layout-tilaa vanhemmalle sivulle (parent) ja ohittaa tietyt nimetyt nimetyt näkymät. Kirjautumistila olisi siis:
<>Copy$stateRegistry.register({ parent: 'layout', name: 'login', url: '/login', views: { 'content@layout': { component: 'login', }, 'header@layout': { component: '' }, 'sidebar@layout': { template: '' } }});
kun taas kojelaudan tila olisi:
<>Copy$stateRegistry.register({ parent: 'layout', name: 'dashboard', url: '/dashboard', views: { 'content@layout': { component: 'dashboard', } }});
Määrittääksesi tilan muille sivuille noudata samaa kaavaa.
Kun tämä on tehty, lisätään console.log
jokaisen komponentin $onDestroy
-koukkuun ja navigoidaan sivujen välillä. Näemme, että otsikko, sivupalkki ja alatunniste eivät tuhoudu, kun navigoidaan /users
ja /dashboard
välillä. Jopa silloin, kun navigoimme sivun, jossa on pääasettelu, ja sivun, jossa on vain alatunnisteen asettelu, välillä, huomaamme, että alatunniste on uudelleenkäytetty.
LoppupäätelmäLinkki tähän jaksoon
Vaikka on mahdollista saavuttaa jonkinlainen asettelun uudelleenkäyttö Angular-reitittimellä, kuten edellä on kuvattu, kumpikin lähestymistapa vaikuttaa hieman ”hakkeroidulta” ja tuskaiselta. On paljon helpompaa saavuttaa se UI Routerilla, jossa voimme jopa käyttää uudelleen jaettuja komponentteja eri asettelujen välillä, tai Reactin dynaamisella reitityksellä.
PS jos tiedät paremman tavan hoitaa tämä Angularin reitittimellä, jaa se kommenteissa 🙂
EDIT:Linkki tähän osioon
Vaihtoehto 3Linkki tähän osioon
Kiitos Alexander Carlsille ja Lars Gyrup Brink Nielsenille, jotka jakoivat ideoitaan kommenteissa, saimme vaihtoehdon 3, joka ratkaisee kaikki edellä mainitut ongelmat. Ideana on tilata reitittimen tapahtumat ja sitten jokaisessa NavigationEnd-tapahtumassa voit näyttää/piilottaa ulkoasun paloja reitin mukaan. Esimerkkejä:
Esimerkki 1
Esimerkki 2 (laiskalla latauksella)
Keskustele yhteisön kanssa.