So we previously setup so we could load a page built with angular which included our menu and our footer and the menu loaded the categories from a service.

We finished up with having a section for our router-outlet and then having imported the routing module previously.

I start with the previous articles suggested link where I modify the routing module app-routing.module.ts:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './about/about.component';

const routes: Routes = [
{path: "about", component: AboutComponent}
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

We also create our About component:

ng generate component about

We change our navbar.component.ts to also now use routerLink not href in the link. When I now click this link I get:

Where the URL has changed only when about is clicked. Let’s now load up some information to it using my About page originally which is mostly static data and hence easiest to start with.

My about.component.html ends up like this:

  <!-- Page Content -->
  <div class="container">

<div class="row">
      <div class="col-md-8 mb-5">
        <h2>Professional Career</h2>
        <hr>
        Prior to working at the UKAEA I spent 5 years at National Instruments where I developed and advanced skills in Yocto and EPICS as side projects assisting customers while in                                             my initial role of technical support and later in my sales role.
                <p></p>After moving to UKAEA only 7 months ago I have worked on several projects requiring skills in EPICS and one involving Yocto however my most notable recent dev                                            elopments have been in Gitlab and learning around CI/CD automation. I will be writing more articles around this in the future to advance this topical area. My skills have advanced i                                            n cmake, make, gcc and how the glibc library works including how the linux kernel boots so there's going to be a few good articles in the near future on these topics as well.
                <p></p> My role is of course as a Software Engineer within the CODAS&IT department who support the integration and maintainence of control and diagnostic equipment f                                            or both reactors MAST and JET.
    </div>
          <div class="col-md-4 mb-5">
        <h2>UK Atomic Energy Authority</h2>
        <hr>
        <p><img src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAOAAAADgCAMAAAAt85rTAAAAkFBMVEX///8AAAAAVpYAWJnp6en5+fng4OD09PSrq6sNDQ38/PxDQ0OMjIzs7Ow9PT3FxcV5eXnW1tbNzc0lJSW8vL                                            yVlZU3Nze2trZvb28aGhqioqJaWlqEhITa2tpjY2NPT08xMTGZmZlISEgiIiJra2tYWFgyMjKvr692dnYjIyMLCwvW3+odaKEAR4+Jh4hiX2BE49cBAAAcVUlEQVR4nO2diZbiuJJApfe87/uGdwx2MTPw/383CskrGCdUkllkd0Wf00Ua2+h                                            aS0RIoTD6XyLoORGRwJ2R6Qgizz956R+Q//znP/998hIpM72znpi5kjhfUqaXytOAti3UppYml/AiWKj6omK9Tp4GFFMxSK1zVKdW6pjaFxXrdfIRoHL1d5iYalhEddRYxcUyBM27OkHgXlvAz8oHgIK+KK9i2m26K6zq7HhSUqjnQ5gJywvU                                            9iuK+fvyAWAjLMcRxz9fmsYPBV03JcvaXbR2fAI8J4pCqElfVdTfkw8A6wYhLkp7CN4RlaQ5hslRNYxjXHXFrkW2yL40jaYydd5pr1v1n5UPAHPMJOdose3m0BwSu8VBZJ3jxNMO3SXV6ZlcQXojL/Ltdaf8w/IBoIfxCQBL1SccomO2XZpwX                                            iyTlrvLnMRKPZM24izHNumBZsIhMcy+qfCPyDYgTzodBSz0VGKATaEhLwjilNsJWVdYIQM08LFDjoAcTzdxI35X8T+WLUCe48TEwKeYEPomAdST6LCrHGS6JsaNLOqOVhwsk5wqYmwYyO5EpEsl7n4IoCJkO8LW0Co0W5Txme1UgYPsvYCyA0                                            ZiFYRORmsQ+inSsxS6qv5OJupmExWSkpTbxoYVSKQl4izztMiyTL5rFMU68InWWIlHcATFIs9AFDnOEQTnrVT9JiAfYhzpOulTFbIVLk1tx2sOne8IbtTlnOeTPuiZpD0mTaLiXetYDcqyd6q/jwYZhfRCFGIfOaIpZJxGSEKrvpjOGeNd2hm                                            F5uVk8LQD7Kq4FB3pzbQ8esjYFmH41Hc2qjLyWQsijDsk7DFGZKjxyEirhC5Vlt9S4GflMW9Cd3RSN0QDIrNrbcLSJgRTSo7YEGC0TawfDsiHSFRElHhI4L3d/tzg0y9CtI+xQcZQXRJQG/9oQNShLHSIHkecH2BbAO1hgHrE8rEwFVB7CX5P                                            3/BBQM63vDDJiVUjy3Z2dgMZ+xEABqGfF81ZSyXfHNTDW1nb22qit0h0ydz1Vndcuv2nUwD/Vwel4CVe7xkqmfBGxugWoKR7jFAgSk6mVO6xpB8OPmYH9hqxrlvPNKVD1Nec6Dnmt5T9IdkCdKsDc4WIl2BXnHkkFciwsO8wL4O00ahyOI3Yp                                            1gdrkt04d4tv1+2AINStRmhKOiJ4+ILMUsPBSZu/r5vp2UAviKP7PRAzAGkiFzWYfnwRtOJW4Ae9LHW8xwRmRr0P8fHgWMS5Y6KHjBN81ZVPA7pTckpZtKxwz+kiaZ0VNmdSlLesNrlDtLwhcDVoBR6Tx9biqU4uhkmJhKp3wFN13+fgXQLkA                                            2b6gknCFofGXA0XMH/PKT3gAU27EJMkKiTtsxXff3t4vdxKDYBibOEf9Wq1WsL3klgJlsBlU61fHxyKs5vrVErtGlKGq+Q4p8BuK9ivA/R5P6IjPRoKUhjujBBnAQHRYd9lcEXe7l4H5d+CzBuZCy3Ip0mE31Nq9jgmEkWlwXDbFsmoqzSor1                                            GmSyviUnlvpHXtAVIbDHLTf0UPqcV0YBuTVQB8tDZzInJ1rtIor/Dck1HToVvtDop4vqH6EEHB8cst4ym4kRcGg7oBrWLSmJ0Hog+VOuiqInhltlY5n3sd4HqFlJcBU7c6N/JsClbgJmaBtGhK9ym3OFczGiVWTpnV07QOKIomiIS8Ikw7nX9                                            dKorv7Tqptk1xDl+G9mcVfMaralLed86qnzgMhxVe6L3kNdUdaUKSJBNcOqxbZU6V8ouSt3UdQ9dZL6PGvxgVu18ICNGWASJgfHOxn4r49JWoL/FxDLLcUDaKsaaUYpFDEoRt0SzxNE3lv9D2fYHzyUxTGKwTYIUM82AK5WMNjBFkVBFT4Vo+                                            6I3w7GB38gS/QiwkpKYFfuSGT6+K42zb1TQ/GCeum80hn4EGEbaCcvg4iZ2IOFdGuOiwC7xfoPWwGUqY4nUodOdbVdKcRlCxbpvZMagjwA5qBYsggkaygKushJrCY70HLfEFPfRGUuE3jM0mxzIcc5a7A8CZE5DF2Fbzys0dDLi67uIt/fEOq                                            17/xdZHUfXEg/kSPJ9xf9YtgGJe1QwDyjaCQOgppiiIFqCI/peb7FVSCjZJ4kMRbvvK/7HsgmoWNiRaLmjmBhpOCDa4nhx20AgznCLMlzGUMdVnSChb54uOaV+IzX4gR484LqmHmwdxynCmqjiM6/GxJiWiQ4kHbQERYgsKSlLA2raLeUTUZX                                            fV/4PZRPQwzJxmPZNavpEkbuGPOg6XE86ECRRccMhXU+Mcl8Hbvo+pui2sd1heS/jgv7hqUSFM6FLvnuMh4k1UO4J9ZaYT1+WUfg2DuEGYGjgM5hovd62S1yT9qhae/kQ4MK54H2NZcKNvXwaV4iLVVQ4Mt4miO0+oHPYBZkGI2QvBe2DGqpB                                            b8AEVK3iXzC/RrRJ2J9zhiVhB7dpgsJCTd+gHlcBbV91eE31MLPT+ukIH1/8PalBF3e+TE5yHWq3wCJT3cfKDBP8uDgd4Z/0z6/2rgGCZjhqRtQXNsUWK+ehGcrvZWKim+3wp59RxcBbMOgyiZvaNd7BalsDhALmMFzItAYr7DL/wExJlyxzM                                            ry0qCPWCwyjEEYTVToF5Mho02v+Ehulq6pvDEiKmHSR0LsIfXhW2NbE4S3wPhfIcV2VsQvBXkP0FocHOiwQGwDLePfnO+EqYN5gU1f1SxH1BlhPkJEGuK9jqhcoB/EqmHFKZQC0ZJxaSDyY6hvMrq0C+hKMklV9Iq3ROE6AxHsYRR6V4DjO6i                                            W0YKzyv3BZIbt2yjcIzFsDrI4FTpEd0SUkzXEP+2ExJRtWP7Fq7ugISsZNebhOvGCcOW6RutjSyaBbHt7AZlsDhA4WEU5mhaEiU8dJiJA4gwUuMiNDvsVB4IE2zVDw/iWlSzYniDwkDvA7TD6tASoVsVnIv25xAlBPPk2zLL5RkYbol6S+DNB                                            6Ee5GXSceqMIv2PMJMT5/B8EHsqroJRxrZMw4El8oQzAzOMVh630jFSBIBqQmRnb/nUnn7MHx1QwbWsA7hFXeAVRFMEvk6JA7UBWGRnuTTXBDNrjsiLUNvoXriGZnQiUKlQGjUUZ8D+tcq4Tak/68IbMOmBB/hyd9SePFOtf5TCqxm5DmablO                                            JpGRRLXBpkkFQlhaXYlh9cIhLfec8eLuKCBewwGY6G/Atw4IYwPf0XKjM12t1dy9JoYwelIvsIL/y9TZd6nn5BVxTKdiWrbwsjPexSW8702c2Uq7sxfESsgks/XjAtd80nXUD2TO76/KjlyVEEberkpaDtUtEsmQ1KjrN/1+uQ+oEV8g5ElXT                                            DKcu0VTlbGBD0Js6TBdHySklcZuIKLMbonR2VpUK/qxxGsWGUmL78W4L/cBK1w2ZVcbe5htgdK3QmEFDXbTok5VnJdYU5tOTMmAecaGTQxxWXVdWa2xwSXvM7N2F1CpZBzXdarR5iiTf+Lct6MiqTPk7ULftcQkDxHntEWW2IboGacilIMyPl                                            gdjoPS+/OOBJV7gFlHVFwnwNyvC9Z1pBZWWoVckvhedTHPEURV2HkopRrpn+a5bc2jAbbPQRcqHPrFbvcehPcAi2iHd7mmtfkBx0cXew6HNMFUpDTz/LaKKiS2vp01vM+d/czpiCslmUlp+DvhsN9HB1HC7xGRdw8wBDeQDJQ5rxt7HHI5CoW                                            8CRMn7/SDE6aibgoCGUK1o6c257Q5SL6j7OoQiQgcfVk9OG9giKKNPpidd4VaEoVAFDnWUG4JKDElzwz90Dyb53Ok6aJUSrlzdoQwTPWocUziIvo5lknzPLxNMNd2vGgmaeeO6HAseZckhMmKuEs6M3GSJoxE0dTCfAd+7ylKErp2Fp3P2tuw                                            Ufk44pcXTK+t69ETxG6eVimpzTbpdr8mDzi2fMl7A+v6Sh4MaRbJ0ELnZ3o/Xg6MYJjHP1ZmVR5bjn+PTnclT29S5sOq6go1OBp1Uat19x5j5X35jX30ICIniFNM9zvLbwL+HPkL+NPlL+BPl7+AP13+Av50+Qv40+Uv4E+XfymgyEGGgGshB                                            1laGV4nn65XVuAYd3eqEL77quWKrd+9A1hh+TasVYH1CLq04gQn+XqPoFOf5BO+F8Glk2tP6hfNlEK57n65DujjlbhdhS58wie6AXQZYJDQ4Oe7+wbTfjl8LqIgCC/xmOHWd7/8PUD3GpBtOZDu8SksAMpafO+XgfuSMIxvAGQ7Du5XR8smqO                                            LFjgqo1fDeFc/I1wPS0G33fkI13sIsim3B8zLAxrKau1++AtCDud/jRmszY4zPwhHjfF7JLwPk+Y00mS8ArGjI3gYfLPfHHoJ1/fkk48sAN+XTgAoN79pcsYbAolqBxFaLvrIGyN+dPV5W0nZm03mNfhqQRiIEmzocghcuiA0GfRWKui7Cnvu                                            KfBivtT1JSyvPnN2MfAutWjDbNJXMPrg685KzJpnZ7KTZJYIZauc09LLXAIY03HB7vxmoSDALoI1a7JB0yRu4ybHJ8wPrmGbax5riPBm6qmjllxaJWh/gX0CF65XK/jL83nA4kHsMv8Wdh80AhuW8ABD2R+Jf29P3/NA0IZWQysqe4pnA4xd3                                            +9mR4YlxAWwbCqYvWpTNdjM0jHDW9O1ydhe30z8JqND2aXxgjkBoNE2mo0OUqc9+oAxowp3YCIJYRH3igaA47AqVru/kDJBcEe3xKSh2Bxqnip0aTtvtCkrCLKcRkKeZl7B6IN/TQLNC/BSgQuuh+SieqR67HjwPZs0QM42DFqtxxF4jt6atL                                            qI3F2kouBwOgHANrSmv38rY0dNsuETmFoDQ2fEhoQWiOe8IxGcA+21NHwCacDJrS5D9wxjbc4qnWGko+2VoCfS+NJCPAfadtP+9QZdC7BheAIo0E9MQo8pR6+MTgI7TN/UPcjnBvTT2EISFNTOpCdq0isnXoLv6zBFweII0ZHoEoGNWOweEPh                                            5PiofuGv8EYA5Pna77bkZmC8XMBj3PK2oC9Gjvmq4RodvCngsKOH5BFcv0OLOhkAMgbGCc22wB8aM+AUibC61FdytyWZoXytvjyZqZAOGcaD5USSfW/CjgWLNKtSiWcA149ZiQLUnS5wA7kbWm/L6ep4HqU9AztNEhDngEVODn/PlVgsF+CgC                                            P483hPHlqLteA15YSk88AQkYuvR76y7pAFRscUpiwlLrXgHqx6DvDg7AZYPAooPBqQBZQ6NDB+66q74f2hQjXgC59CHMp2FN7CtB+MeCgHqh6uheYpq/wDQPBCMgRd7heKpvfBYwfBKzWAPkl4Ngs041ueF4D7LdhTDVI+mmwzC7wu4AyupZ1                                            QLr1+NpxobWhj4CjA6jQ+Yj1+TQoaCElo0gVXJpeARKz6teyG/9OH8yeaKJgWdykLaIqgb8FZHcO1nQFPXFZN/Ds1GwBuD2KPgzIXQOKtm3fAdRVfOuj52MTuAKk+mlS4DOBybbD8jgdlMIFINWDiz0W8BRk7knAGz0Iv35v6h6aiLzsVtQTC                                            dcAkUhH//TmLtwFzzeVUNHBwqL25AQITld8z5J5HBAeXT77KbwBmMxGu/52UKl9s72ZNrT3109vLPjpWkfSKVQOzY1tmnFu5nVRqxq+eg4QnIl4Nhe2Bcg2SdaTeqOe7QB1O/FLb3ZdWTDZdDXZSyQDT83qAfvcOmzedCCkLZ5e9xygDv7DcX                                            jMnkxLdA+QpekoW1tUkMJn1NMaDf5bQGaiFEttyJ3wirahzR8KRP0+zzRhjxrsVMCqwylkZKDHT6M/+Dggu2PZkrvwegJl/KXfBeydY3gtQWp1bBbEHSp0BVCH7oa1RXVBFdW3Qw/VjcK4YfQENccSYp12VhqxPV+J8huAOttXfSBFvrC7bG0                                            rWEybUBl/aWXxBTlHfHWQX2m1tNS4b5vz+wqLn4v7jgtOa7kAxFuAiPfmd3EhbHxrAbSqj+PJ6mHm9dmFqhrXg4ekEpmbSpqhquXafclxNedhSrWuyceyT0PbsLTIp2Mx/pSeq+puAkwCtZ6McqFUA/qXq6rucJA/q2z2ylWZ6bG9wpslmhbl                                            jeYni5amiERuZmjFq6P077W78tMXyvwc3Ut8v5LmhvvyFvzNDygrJ9G7JMPz/3gJW9R/QNjrffmXrtH/g+Qv4E+Xv4A/Xf4C/nT5C/jT5S/gT5d/KWCC16T+/uJ9XtYBJXkN8G2yGzwjzwAebk98f9kAjK9Evh/S98ZyH/Bt8kt+Tu4DvtFrT                                            T4jfwF/ujwLyDZUKLoTSklrCosFKEUXTCmRvCmWXun3Xygc+YLrD9lwqQP7LkS64YG73qShbG6EeFKeBcSw5KUPGfBxMVtRsqshfObQ9oue3JHex6YrSnSmMvOHqELLhrl9Q6TLGvNVLxaV+AcBPRRNujEeJ7idWdTj8M43WFvwh4yIcGarTu                                            eUokbDK29WZuH21h8EbBfLCMPK7JT+lko3A+xjOM1+SWuU3GLxo9Ac9OVvvC7bzrN6ENPk0yUNvHJodbDlFZrpvtQ4ehgw0hEQFnrcYnfJUEbTktJr7T4AFgDtZRulId6v4tsAjMwrGUMX+yACMkic5WH1B8Ip0uGx+6ROaLp0ACxlbJxpV6U                                            LomO0IA2zpICwcFlOVQYt9HW7nLZs0dPiP7ZwRTvPGFDBq31hYNH5MLZpHgYVaOIsHDLoCw9VM1tidgZA6ryMK/wQC/DC7M5PGNvjWtysOe16QKjA2YAKe5WgZhnguKh81fZGQHPWKuga/gszy/4O4OzEvjkBSDwfJ4Clj3OZwqugTc6jZ5MB                                            UM8nKIjCOL4ws+x9wEJbynl4qfd8kbMHhMAha96sRFZiCjgu8uKr0VIYAGlsXD9qm3gtIOX35TdG0bkO7gGhgHU6E1jht3rAIdKJv6p9qgAZYFaOS9XSdWjlJ+U39OAKoLbSonFwFbF7o9AhyrDfRwF9mLZLiLFSX5mX9AsBMQPcD4DQIhfRg                                            DPAZBh/+gTKr5PXAQZNtBTrKgzkJiJ3aqIsFhU+hHglYuoz8hpAunVAF6/kCvAmpnoGSPUNtNEAr4REfkZeA0jjS1fOXgbyYLw0UZwZoM2aJv/iMfRVgDA0LF5sqti2fV2D1Kie9698Bsgz16m6CXL8rLwGcBoFe/HwSsRuhRevLKL9bgBEdg                                            D7lYqXv5bqRYAwylxmx/PJwpkAr7be+AtAvoPE1sbL32n0GkXPlPhohinAe74JaKWbp8ZtMnR3ywQIz8gt4llm+dfIhqnWhldi3gdEAt3JFHIiL+r2YQBZAvZ7LM4w5QLq4FTOAUXm7L/6xWnPTN0bG4DIY/MShWaxqYuVeM9pCyCTJJ0DIvo                                            e7ZcnIH8GsN4CvFqRalfiPYnYM0JNaRaAdOfKYvbp6wDXl88ChQKWsxNhMOmNaSWcndpvEuDcK/WBhHw8RQFPfgZI9eT9dAq/KXfyyQhrAiXt/xlEJ39Po62eXIqiOKTTTgk4/Wo0VgQrihKoVg7fOsCvYFrIn1zhBeN0tiQHhs3LZgtH+U5A                                            oaqqeZwwzEHNPHxovK/PN/udgOCsz60BvCCCCSz39VmRvrUG8XxGDlXLPre6pe/z8p2A1JKpQ1pLoqOBKmLh54IjZhLRKMUXvMH3WwcZuvUd52fN1zpqF/Rb+KS4gbmN/Ve8gvl7R1FnnrCi392C+h0rX/R62+9WE8Pb1IgBMO04ZMkdviaI4                                            9v1YFZpqWVZmj97TaitnTUt/JoV5T+i6O/n/Xm9/Etj1f5B8hfwp8s2oGIddrvnl5OV6rC7/P7kkZIUh+hVa7zbgNRc3M4XM4k+pB+lM2QbCTw+EpgjLYXxnp+bhdp+50ufBOohG1+sT6eSG6+72X39uPDnMfVDjuVPulCbgHa/A/ShwEOYFT                                            tNgJ+pwQkQ5uc+N1G6CUh3xJ6uFm/vCQDKr6lBcnnMAHdfCgj79/fwlrqHauMK8BM1iCC/DvvwtYAsIkfDK5lJVuR1NTiTrwWEyUOHOnHlA8PMK2twlK8FPLEphfrBHyHnxcxDmGow05r0dqqTb62oWn8A2Tmfu/UXfJ2u5VnZAGz78RNmr7v                                            xKLykfjYVpgexXPB8Icd0Mhxi1y9DDXJh7/xF9qyJ6+OsspYNg1foxjDXr9Ncs7biY1nlUCPH9D1W5J8Yw6tzT908rNQhv7zIOPssoDiEgCyTQXnLuT7IV6GK4ixKEqt9DTrT27JnC0/SPH+k1VejROe0WXAfAUypmjjMvX/kyFfNPn3MBLkP                                            CBlCWKwkxrMp9WtAGikhHmdlObJxvqPTE2XPM8xO9KGY+wGTxTfBWkjI9cdshenBWZ5NAkhzzMyAMhU/tJZ4H3DKZwOfxvmEVUDkmGZLE+qYpmmzGsQQD8zpOkcnJPqVXbqCVJoCp3NslYIF6gGgRP8uNUnsFb1tmrROfXJzk00rzmwqKMdNK                                            pdnACHjiMymuSBVyJg1ax0QsWRv/fp6b+IN0QSgaI4UhHa/Me8EDRy2BkAD3jlKr59ZMjCK9qaaPo+aYmmWHrGw7gICSMPKSzMaVbPjq4DXamKKlhChscGUGc2+qI6F5MGGoFNPbLluyJt+BTi0wwbPJt4gI1/wyPh6D1CMZiDzcMeHAWcLYz                                            CGwIIaXeGdaQ3oR7RfMcBhWvQO4CIUzMYP7qW6B8iNzQr1iaakJwFns5xhf81N9i1ovIXeA45rn3cA0fyuGn4wSfdWyqPpCdGAiCnL60OA81Tx7Boa0rzIn0ZHSq4HHN2ie4Dw0I1edc5r87cA4QYdTbIOWblnBXgYcKaz+mtgvfdq+rpfMqP                                            3H334e4AQbuQ6I+tjE8V3AGlYiGoNcjgN493DgKcVwHiea5SKxTolALofAtJm1Wd5xo8au3cAV5MufhoQ34x8KXtwADg5nXcBzXFkL9Yz8T0MuDC9RpGeAVxroniKwJ8BSgzw/DGgMvRtoqTjB5ei1gHBTLvMA7Z90LLFGiB+ogb3N+HKESvx                                            g4A0Oor4pmCsRo9V4DogTcW3DO2HEZ72n0cBV2qQ6ta7o+gjgGDNGDp1UR/doLoKCG3dWBqyNFy3ugUUnqhBmhftPL/rkGT8UcD+gRC96j7qT68BUn11FZZKkxVDs/CWhUyfqMGrTOnkPKhSmA55GBBCTRL+8sSO8DVAGj1+7YeDTwC1QkMlx                                            hGdhilPgKfNGmSmWTq5AGMWyocBachzhh+ejF4HHHekzIVunyNa2jZmt2fJBHtvAko/8yZWAFl0/tg2vOMw7G8CLoZLqMILfiIiag0wXjUToAORDk5HihP7eYe9TIAB0ncr5SPgWg0i5tP24UxsGym1bO4C0k1781L0npiLHpUVQJqs+LYP0w                                            yx5rBekXteCC77oVts7qC892twSEGet57G4g4Z7F3AcS/meC8WsPB4PMYKoLa44SQFOy7ONrjigtNGQGkBuFqDpH8vNorG/WTWXUDhBpB2kSciov6PyPIInV9ZC+1P+hbFJcNu3dNZAJ177Ptrv0m5ZKPjDDCc3VEftzITaEeZbj3tGYRRfN+                                            bdG19BUgr9Ykt8P9DZHmEz9iWgBvhyXGb/q7uaIZqdHSrOGfbWf9zis5x7BRheQvIBz29c0B3wk5VVc2cFsb0+QnsnsN35J5wt+nL83xn9D9QYLPwB6/w+NlC+vP+hRtE305gx8KPzEz0qIzvQfkHimDrbF3jTxfkq6TCAdUw3cen/kxhJtT1                                            u0X+QcIA6+fWQ/8foojpRzjn5MgAAAAASUVORK5CYII=" alt="UKAEA Logo" width="224" height="224"></p>
      </div>
    </div>
    <div class="row">
    <div class="col-md-8 mb-5">
        <h2>Education</h2>
        <hr>
        During my time at University I overall scored a 2:1. Admittedly, I'm not great at examinations and some of them had weird solutions popped in where it was overcomplicated by                                             picking equations only solveable by three levels of identities (both exponential and trigonometric). I love mathematics but I dare someone to solve that in 15 minutes!
        <p></p>Anyway, where I really excelled was pulling back in my programming, in my first year I received a first as most of the coursework was programming involved and later h                                            ad successes in other subjects where I applied this further, in one I used genetic algorithms to solve for the best plane design, in my dissertation I resorted to the Euler method t                                            o create a simulator for my project as Star-CCM+ couldn't compute Mach 3 or above whereas the math worked out.
        <p></p>I found university a great experience, albeit with it's trials in exams, however I opted to exit my masters early as I already had a role lined up with NI from my int                                            ernship and my understanding of the 4th year was a "preparation for industry", since I had a year of experience in this regard anyway (Year in Industry) I opted for a Bachelors.
        <p></p>For my future, I aim to work on a MBA when I have the time and money available to do so, my current plan is to pay off my student loan which should be within 2-2.5 ye                                            ars. At which point, I can receive funding for a distance learning or something of that regard.
      </div>
          <div class="col-md-4 mb-5">
        <h2>University of Leeds</h2>
        <hr>
        <p><img src="https://cdn.awpwriter.org/uploads/program_images/program_2510.jpg" alt="NI Logo" width="200" height="200"></p>
      </div>
      </div>
      <div class="row">
      <div class="col-md-8 mb-5">
        <h2>Personal Life</h2>
        <hr>
        In my personal aspirations I have always strivven to learn more and also do more. I commonly take on fitness challenges such as 45 days of running 5km a day in lockdown 1 fo                                            r WWF, running 5k per day in January 2021 for the Newbury Soup Kitchen.
        <p></p>I like to keep myself in good shape and am a strong believer we have evolved to be active beings, while I work in an office, I also work outside of it to counter bala                                            nce this. I've attended in several races with time goals.
        <p></p>Since the age of 12 I also began my programming, self taught and starting out on web development, within a few years I moved to C# and application development. As my                                             curiosity grew, by 17 I was onto C and Assembly as I was following osdev.org in order to build my own OS. The intent being, if I control the threading, I can maximise the capability                                             of my hardware with the code I need to run.
        <p></p>Most recently in the past few years, I've embraced build systems and CI+CD (Continuous Integration and Continuous Deployment), learning about Yocto and using it to bu                                            ild Linux images for my raspberry pi as well as NI systems.
      </div>
          <div class="col-md-4 mb-5">
        <h2>Personal</h2>
        <hr>
        <p><img src="https://cdn5.vectorstock.com/i/1000x1000/05/44/running-man-logo-or-badge-silhouette-of-vector-14630544.jpg" alt="NI Logo" width="200" height="200"></p>
      </div>
      </div>
    </div>
    <!-- /.row -->
    <!-- /.row -->

  </div>
  <!-- /.container -->

So now we have the about content, what we get when we click About is:

Well, we’re getting there. Now we need to figure out what is different from my about page to this one and why the css loads it in this way.

I did miss out my “header” for this page and add this back into the about.component.html:

  <!-- Header -->
  <header class="bg-primary py-5 mb-5" style="width:100%">
    <div class="container h-100">
      <div class="row h-100 align-items-center justify-content-center">
        <div class="col-sm justify-content-center">
          <img src="./images/win.jpg" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/download.png" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/teamwork.png" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/honest.png" width="100" class="center" />
        </div>
      </div>
      <div class="row h-100 align-items-center justify-content-center">
        <div class="col-sm justify-content-center corevalue">
         <p></p><p> I never lose, I either win or I learn
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> Learning is earning
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> Teamwork makes the dreamwork
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> The badge of honesty is simplicity
        </div>
      </div>
    </div>
  </header>

The result is a little better where my content is actually visible:

But I also get this behaviour:

So our method of setting the footer position needs fixing. As well as the CSS for our header. Which one to start with, I think I’ll go with the header and use inspect element to compare the angular html content versus the original.

In our newer bootstrap in angular, some of the properties are computed and have different results for specific class. Starting with bg-primary the background color is different so let’s simply hard code that into the html.

With this change:

  <!-- Header -->
  <header class="py-5 mb-5" style="width:100%; background-color: #435165 !important;">
    <div class="container h-100">
      <div class="row h-100 align-items-center justify-content-center">
        <div class="col-sm justify-content-center">
          <img src="./images/win.jpg" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/download.png" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/teamwork.png" width="100" class="center" />
        </div>
        <div class="col-sm justify-content-center">
          <img src="./images/honest.png" width="100" class="center" />
        </div>
      </div>
      <div class="row h-100 align-items-center justify-content-center">
        <div class="col-sm justify-content-center corevalue">
         <p></p><p> I never lose, I either win or I learn
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> Learning is earning
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> Teamwork makes the dreamwork
        </div>
        <div class="col-sm justify-content-center corevalue">
          <p></p><p> The badge of honesty is simplicity
        </div>
      </div>
    </div>
  </header>

I got:

Now we’re just missing our images, we should add these into our assets folder and then just rebuild.

Then I update my src links for the assets folder by appending /assets/ to the front.

Much closer now to our expected page format. For now I inject a margin top of 40px to realign the about content but I do need to solve this, for now, let’s switch to the footer part.

Setting the footer position to relative and encasing it in a container as suggested here seemed to be the most reasonable method as all my pages would eventually have content for the footer to sit under:

html, body {
   margin:0;
   padding:0;
   height:100%;
}
#container {
   min-height:100%;
   position:relative;
}

app.component.css

<div id="container">
<app-navbar></app-navbar>
<router-outlet></router-outlet>
<app-footer></app-footer>
</div>

app.component.html

So now that that is done and we now how one page with a route, let’s move onto our index page, a more complex beast which includes at least one new service request for our wordpress service. So let’s make a start:

ng generate component home

I also update my app-routing.module.ts file to include this new route:


const routes: Routes = [
  {path:  "", redirectTo:  "index"},
  {path: "index", component: HomeComponent},
  {path: "about", component: AboutComponent}
];

Okay now that we have this in place and I’ve modified my navbar component so that rather than href for this menu item, we use routerLink instead to “/”. Don’t forgot to import the new module of course here.

With our new routing mechanism we should end up on the main page with the URL:

So now we need to flesh out our home page. First we paste our index.php contents into the home.component.html file:

<!-- Header -->
  <header class="bg-primary py-5 mb-5">
    <div class="container h-100">
      <div class="row h-100 align-items-center">
        <div class="col-lg-12">
          <h1 class="display-4 text-white mt-5 mb-2">My Portfolio</h1>
          <p class="lead mb-5 text-white-50">Welcome to my portfolio! This is an accumulation of my projects that I believe are noteworthy or undocumented in their current fashion and deserve a place where they can be shared with the world.</p>
        </div>
      </div>
    </div>
  </header>

<!-- Page Content -->
  <div class="container">

    <div class="row">
      <div class="col-md-8 mb-5">
        <h2>What I Do</h2>
        <hr>
        <p>Almost everything, here you'll be able to see exhibits showing front and back end web development using a multitude of tools and modules. Alongside this there'll be examples of application or kernel coding for both Windows and Linux to fit on consumer electronics from PC's, Laptops to then also embedded devices where I am using a Raspberry Pi for testing and a standard PC for building with Linux.</p>
        <p>Examples will be both basic and advanced, as with anything, I have areas where there is basic knowledge to intermediate and advanced. Each project is not only a demonstration of my skills but likely something new I have learned.</p>
                <p>Feel free to contact me if you have any questions, suggestions for a new project or would like to understand more around a particular project/field.</p>
        <a class="btn btn-primary btn-lg" href="contact.php">Contact Me &raquo;</a>
      </div>
      <div class="col-md-4 mb-5">
        <h2>Contact Me</h2>
        <hr>
        <address>
          <strong>Edward Jones</strong>
        </address>
        <address>
          <abbr title="Phone">T:</abbr>
          <br>
          <abbr title="Email">E:</abbr>
          <a href="mailto:edjjones17@gmail.com">edjjones17@gmail.com</a>
        </address>
      </div>
    </div>
    <!-- /.row -->

    <div class="row" id="cards">
        <?php
            include './php/cards.php';
        ?>
      <!--<div class="col-md-4 mb-5">
        <div class="card h-100">
          <img class="card-img-top" src="" alt="">
          <div class="card-body">
            <h4 class="card-title">Card title</h4>
            <p class="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sapiente esse necessitatibus neque sequi doloribus.</p>
          </div>
          <div class="card-footer">
            <a href="#" class="btn btn-primary">Find Out More!</a>
          </div>
        </div>
      </div> -->
    </div>
    <!-- /.row -->

  </div>
  <!-- /.container -->

Note that we dynamically load our data from a PHP file under the php folder called cards and also include a commented basic template here which was commenting from previous me which is somewhat useful now. We need to replace our PHP method for starters and use our service here and then populate the cards.

Our cards.php file is simply:

<?php
    $json = file_get_contents('http://www.edward-jones.co.uk/blog/wp-json/wp/v2/posts?per_page=3&_embed');
    $posts = json_decode($json);
    foreach ($posts as $p) {
        echo '<div class="col-md-4 mb-5"><div class="card h-100"><img class="card-img-top" title="', $p->title->rendered,'" alt="', substr($p->excerpt->rendered,0,100),'..." src="', $p->better_featured_image->media_details->sizes->medium->source_url, '"><div class="card-body"><h4 class="card-title">', $p->title->rendered;
        echo '</h4><p class="card-text">', substr($p->excerpt->rendered,0,100);
        echo '...</p></div><div class="card-footer"><a href="article.php?id=';
        echo $p->id;
        echo '" class="btn btn-primary">Find Out More!</a></div></div></div>';
    }
?>

So, easy enough, lets get our data as we did in the previous with our wordpress service. We simply need to start by adding a function to the service for getting this information:

  getCards() {
    return this.http.get<Array<Card>>('http://www.edward-jones.co.uk/blog/wp-json/wp/v2/posts?per_page=3&_embed', {responseType : 'json'});
  }

Obviously we also need to design our response so we need to create the following interface in our service:

I give Card a simple definition:

export interface Card {
  id: number;
  date: string;
  date_gmt: string;
  modified: string;
  link: string;
  title: string;
}

And then I setup my home.component.ts similar to our navbar was to get data:

import { Component, OnInit } from '@angular/core';
import { WordPressService, Card } from '../wordpress.service';

@Component({
  selector: 'app-home',
  templateUrl: './home.component.html',
  styleUrls: ['./home.component.css']
})
export class HomeComponent implements OnInit {

  cards: any;

  constructor(public wpService: WordPressService) { }

  ngOnInit(): void {
     const req = this.wpService.getCards();
     req.subscribe(
  (result: Array<Card>) => {
    console.log('success', result);
    this.cards = result
  },
  (error: any) => {
    console.log('error', error);
  });

  }

}

When I navigate to this page, I can then also see my data now:

So even though my card object doesn’t contain everything, I can still access it. Now let’s get it into our home.component.html to load our cards up:


    <div class="row" id="cards">
      <div *ngFor="let a of cards" class="col-md-4 mb-5">
        <div class="card h-100">
          <img class="card-img-top" title="{{a.title.rendered}}" src="" alt="" >
          <div class="card-body">
            <h4 class="card-title">Card title</h4>
            <p class="card-text">Lorem ipsum dolor sit amet, consectetur adipisicing elit. Sapiente esse necessitatibus neque sequi doloribus.</p>
          </div>
          <div class="card-footer">
            <a href="#" class="btn btn-primary">Find Out More!</a>
          </div>
        </div>
      </div>
    </div>
    <!-- /.row -->

Here I’ve removed the old PHP code and uncommented the block beforehand. I introduce an ngFor loop where we create a card div for every item in cards we retrieved, I end up with:

It’s a start but it’s not quite there, it didn’t load our title on our first attempt and so we aren’t able to retrieve the collected json data elements into our html. But, we do create a card for each returned item so the data is iterable to the expected amount.

This was actually a silly mistake though, when I inspected the element, I realised I only put this into the image title, but now let’s apply all our data to each card:

    <div class="row" id="cards">
      <div *ngFor="let a of cards" class="col-md-4 mb-5">
        <div class="card h-100">
          <img class="card-img-top" title="{{a.title.rendered}}" src="{{a.better_featured_image.media_details.sizes.medium.source_url}}" alt="{{a.excerpt.rendered.substring(0, 100)}}" >
          <div class="card-body">
            <h4 class="card-title">{{a.title.rendered}}</h4>
            <p class="card-text">{{a.excerpt.rendered.substring(0, 100)}}</p>
          </div>
          <div class="card-footer">
            <a href="#" class="btn btn-primary">Find Out More!</a>
          </div>
        </div>
      </div>
    </div>

And now my data is starting to load up more sensibly:

Okay so this bootstrap is slightly annoying in how different it is. Eventually I decided on using this implementation:

https://stackoverflow.com/questions/43042936/how-to-customize-bootstrap-4-using-angular-cli

This has transitioned me into starting to use scss and from the documentation here:

https://getbootstrap.com/docs/4.0/getting-started/theming/

Hey presto, not only did we fix the color scheme we also accidentally fixed the menu to match!

There’s a little bit of styling left to do but now that we’ve got a handle on that let’s switch back and finish up our final 3 pages:

ng generate component categories
ng generate component article
ng generate component contact

Now that we have these individual pages as well we should update our routing module as well:

import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { HomeComponent } from './home/home.component';
import { CategoriesComponent } from './categories/categories.component';
import { ArticleComponent } from './article/article.component';
import { ContactComponent } from './contact/contact.component';



const routes: Routes = [
  {path:  "",redirectTo:  "index"},
  {path: "index", component: HomeComponent},
  {path: "about", component: AboutComponent},
  {path: "categories/:categoryId", component: CategoriesComponent},
  {path: "article", component: ArticleComponent},
  {path: "contact", component: ContactComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

So now we have all our page components created, we should finish them up and create them. From this stage, given we have implemented index, there’s not much more to get categories working.

I also modify the navbar.component.html to forward this id in the link:

<a ngbDropdownItem *ngFor="let a of rows" [routerLink]="['/categories', a.id]">{{a.name}}</a>

I found this useful in getting towards this solution:

https://angular.io/start/start-routing

When I rebuild and test the routing I can see that this gets passed through:

So now I need to implement my categories page. Let’s start by adding to our wordpress service again another function but this time with a parameter. I mainly relied on this documentation throughout this section in handling the routing of parameters for this function.

https://angular.io/start/start-routing

Given this documentation and our previous work, this is what we end up with:

app-routing.module.ts


import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AboutComponent } from './about/about.component';
import { HomeComponent } from './home/home.component';
import { CategoriesComponent } from './categories/categories.component';
import { ArticleComponent } from './article/article.component';
import { ContactComponent } from './contact/contact.component';



const routes: Routes = [
  {path:  "",redirectTo:  "index"},
  {path: "index", component: HomeComponent},
  {path: "about", component: AboutComponent},
  {path: "categories/:categoryId", component: CategoriesComponent},
  {path: "article", component: ArticleComponent},
  {path: "contact", component: ContactComponent},
];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})
export class AppRoutingModule { }

This is how we pass the parameter. Then in our categories.component.ts:

import { Component, OnInit } from '@angular/core';
import { WordPressService, Article } from '../wordpress.service';
import { ActivatedRoute } from '@angular/router';

@Component({
  selector: 'app-categories',
  templateUrl: './categories.component.html',
  styleUrls: ['./categories.component.css']
})
export class CategoriesComponent implements OnInit {

  articles: any;


  constructor(public wpService: WordPressService, private route: ActivatedRoute) { }

  ngOnInit(): void {
     // First get the product id from the current route.
  const routeParams = this.route.snapshot.paramMap;
  const categoryIdFromRoute = Number(routeParams.get('categoryId'));
     console.log(categoryIdFromRoute);
     const req = this.wpService.getCategoryId(categoryIdFromRoute);
     req.subscribe(
  (result: Array<Article>) => {
    console.log('success', result);
    this.articles = result
  },
  (error: any) => {
    console.log('error', error);
  });
  }

}

So here we call our wordpress service and pass it the id, which gets easily utilised by our new function in wordpress.service.ts:

  getCategoryId(id: number) {
    const url = '/blog/wp-json/wp/v2/posts?categories=' + id;
    return this.http.get<Array<Article>>(url, {responseType : 'json'});
  }

And our article defined as:


export interface Article {
  id: number;
  date: string;
  date_gmt: string;
}

With our final bits being the categories.component.html:

  <!-- Header -->
  <header class="bg-primary py-5 mb-5">
    <div class="container h-100">
      <div class="row h-100 align-items-center">
        <div class="col-lg-12">
          <h1 class="display-4 text-white mt-5 mb-2">

              </h1>
          <p class="lead mb-5 text-white-50" id="desc"></p>
        </div>
      </div>
    </div>
  </header>

  <!-- Page Content -->
  <div class="container" id="container">
    <div class="row" *ngFor="let a of articles">
         <div class="col-md-8 mb-5 titlecontent">
            <h2>{{a.title.rendered}}</h2>
            <hr>{{a.excerpt.rendered}}<p></p><a class="btn btn-primary btn-lg" href="article.php?id={{a.id}}">Read More &raquo;</a></div>
            <div class="col-md-4 mb-5 imagecard">
               <h2>&zwnj;</h2><hr><img class="card-img-top" title="{{a.title.rendered}}" alt="{{a.excerpt.rendered}} ..." src="{{a.better_featured_image.media_details.sizes.medium.source_url}}">
            </div>
    </div>
    <!-- /.row -->
    <!-- /.row -->
  </div>
  <!-- /.container -->

And now I can load up my categories page:

So one thing we need to fix is why my html content is appearing as text and not html, but we’ll fix that later. Next let’s do the articles page, given we already have all the components (pun) for this established previously, I won’t go into the details, everything is already here described and I’m simply going to reapply this.

One final thing I noticed when playing around with this is that after the first load of category with an id, when you change to another it doesn’t reload. I learned this is a common issue:

https://stackoverflow.com/questions/49155895/how-to-activate-routereusestrategy-only-for-specific-routes

Eventually I went with the long explanation given here:

https://stackoverflow.com/a/41515648/14860365

However I got alot of errors to begin with, probably because that code uses an older version which does less checking. So I found these two articles which helped reduce this down:

https://bobbyhadz.com/blog/typescript-object-is-possibly-null#:~:text=The%20%22Object%20is%20possibly%20’null,not%20null%20before%20accessing%20properties.

https://bobbyhadz.com/blog/typescript-type-undefined-cannot-be-used-as-index-type#:~:text=The%20%22Type%20’undefined’%20cannot,indexing%20the%20object%20or%20array.

The final part being:

https://stackoverflow.com/a/59715225/14860365

Now that I could get it to build, I still didn’t get the desired behaviour that it would reload when the category changes but I do now get a decision making output in the console.log:

So I just need to tweak this to make the right decisions. Here is my current code for the re-use strategy:

/**
 * reuse-strategy.ts
 * by corbfon 1/6/17
 */

import { ActivatedRouteSnapshot, RouteReuseStrategy, DetachedRouteHandle } from '@angular/router';

/** Interface for object which can store both:
 * An ActivatedRouteSnapshot, which is useful for determining whether or not you should attach a route (see this.shouldAttach)
 * A DetachedRouteHandle, which is offered up by this.retrieve, in the case that you do want to attach the stored route
 */
interface RouteStorageObject {
    snapshot: ActivatedRouteSnapshot;
    handle: DetachedRouteHandle;
}

export class CustomReuseStrategy implements RouteReuseStrategy {

    /**
     * Object which will store RouteStorageObjects indexed by keys
     * The keys will all be a path (as in route.routeConfig.path)
     * This allows us to see if we've got a route stored for the requested path
     */
    storedRoutes: { [key: string]: RouteStorageObject } = {};

    /**
     * Decides when the route should be stored
     * If the route should be stored, I believe the boolean is indicating to a controller whether or not to fire this.store
     * _When_ it is called though does not particularly matter, just know that this determines whether or not we store the route
     * An idea of what to do here: check the route.routeConfig.path to see if it is a path you would like to store
     * @param route This is, at least as I understand it, the route that the user is currently on, and we would like to know if we want to store it
     * @returns boolean indicating that we want to (true) or do not want to (false) store that route
     */
    shouldDetach(route: ActivatedRouteSnapshot): boolean {
        let detach: boolean = true;
        console.log("detaching", route, "return: ", detach);
        return detach;
    }

    /**
     * Constructs object of type `RouteStorageObject` to store, and then stores it for later attachment
     * @param route This is stored for later comparison to requested routes, see `this.shouldAttach`
     * @param handle Later to be retrieved by this.retrieve, and offered up to whatever controller is using this class
     */
    store(route: ActivatedRouteSnapshot, handle: DetachedRouteHandle): void {
        let storedRoute: RouteStorageObject = {
            snapshot: route,
            handle: handle
        };

        console.log( "store:", storedRoute, "into: ", this.storedRoutes );
        // routes are stored by path - the key is the path name, and the handle is stored under it so that you can only ever have one object stored for a single path
        this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''] = storedRoute;
    }

    /**
     * Determines whether or not there is a stored route and, if there is, whether or not it should be rendered in place of requested route
     * @param route The route the user requested
     * @returns boolean indicating whether or not to render the stored route
     */
    shouldAttach(route: ActivatedRouteSnapshot): boolean {

        // this will be true if the route has been stored before
        let canAttach: boolean = !!route.routeConfig && !!this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''];

        // this decides whether the route already stored should be rendered in place of the requested route, and is the return value
        // at this point we already know that the paths match because the storedResults key is the route.routeConfig.path
        // so, if the route.params and route.queryParams also match, then we should reuse the component
        if (canAttach) {
            let willAttach: boolean = true;
            console.log("param comparison:");
            console.log(this.compareObjects(route.params, this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].snapshot.params));
            console.log("query param comparison");
            console.log(this.compareObjects(route.queryParams, this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].snapshot.queryParams));

            let paramsMatch: boolean = this.compareObjects(route.params, this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].snapshot.params);
            let queryParamsMatch: boolean = this.compareObjects(route.queryParams, this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].snapshot.queryParams);

            console.log("deciding to attach...", route, "does it match?", this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].snapshot, "return: ", paramsMatch && queryParamsMatch);
            return paramsMatch && queryParamsMatch;
        } else {
            return false;
        }
    }

    /**
     * Finds the locally stored instance of the requested route, if it exists, and returns it
     * @param route New route the user has requested
     * @returns DetachedRouteHandle object which can be used to render the component
     */
    retrieve(route: ActivatedRouteSnapshot): DetachedRouteHandle {

        // return null if the path does not have a routerConfig OR if there is no stored route for that routerConfig
        if (!route.routeConfig || !this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : '']) return null as any;
        console.log("retrieving", "return: ", this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : '']);

        /** returns handle when the route.routeConfig.path is already stored */
        return this.storedRoutes[route?.routeConfig?.path != undefined ? route?.routeConfig?.path : ''].handle;
    }

    /**
     * Determines whether or not the current route should be reused
     * @param future The route the user is going to, as triggered by the router
     * @param curr The route the user is currently on
     * @returns boolean basically indicating true if the user intends to leave the current route
     */
    shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
        return future.routeConfig === curr.routeConfig;
    }

    /**
     * This nasty bugger finds out whether the objects are _traditionally_ equal to each other, like you might assume someone else would have put this function in vanilla JS already
     * One thing to note is that it uses coercive comparison (==) on properties which both objects have, not strict comparison (===)
     * Another important note is that the method only tells you if `compare` has all equal parameters to `base`, not the other way around
     * @param base The base object which you would like to compare another object to
     * @param compare The object to compare to base
     * @returns boolean indicating whether or not the objects have all the same properties and those properties are ==
     */
    private compareObjects(base: any, compare: any): boolean {

        // loop through all properties in base object
        for (let baseProperty in base) {

            // determine if comparrison object has that property, if not: return false
            if (compare.hasOwnProperty(baseProperty)) {
                switch(typeof base[baseProperty]) {
                    // if one is object and other is not: return false
                    // if they are both objects, recursively call this comparison function
                    case 'object':
                        if ( typeof compare[baseProperty] !== 'object' || !this.compareObjects(base[baseProperty], compare[baseProperty]) ) { return false; } break;
                    // if one is function and other is not: return false
                    // if both are functions, compare function.toString() results
                    case 'function':
                        if ( typeof compare[baseProperty] !== 'function' || base[baseProperty].toString() !== compare[baseProperty].toString() ) { return false; } break;
                    // otherwise, see if they are equal using coercive comparison
                    default:
                        if ( base[baseProperty] != compare[baseProperty] ) { return false; }
                }
            } else {
                return false;
            }
        }

        // returns true only after false HAS NOT BEEN returned through all loops
        return true;
    }
}

Now according to our code it should evaluate whether our parameters are different and return to reload as we expect, so something is going wrong here.

What I noticed from the console.log is that it must be executing shouldReuseRoute and that this function didn’t compare parameters and just did a straight comparison of the two objects. Instead I modified this function to be the below and it works, now my categories reload when someone changes the selected category:

shouldReuseRoute(future: ActivatedRouteSnapshot, curr: ActivatedRouteSnapshot): boolean {
        console.log("deciding to reuse", "future", future.routeConfig, "current", curr.routeConfig, "return: ", future.routeConfig === curr.routeConfig);
        let paramsMatch: boolean = this.compareObjects(future.params, curr.params);
        let queryParamsMatch: boolean = this.compareObjects(future.queryParams, curr.queryParams);
        return (future.routeConfig === curr.routeConfig) && paramsMatch && queryParamsMatch;
}

Brilliant, so now we have 3 of the 5 pages converted – with one minor issue to resolve across the board but doesn’t make too much of an issue. Let’s now go on to our articles page. I’m not going to go through this as we’ve already demonstrated all the skills here to get that operational… Well, except one thing, but, let’s leave that for a part 3. This part 2 was mainly about routing and we’ve done all of this now, there’s no need to bundle more into here. What we’ll do with the next one is just a wrap-up session.

As an FYI: In a later article I want to setup a gitlab instance on my server again and then use this alongside angulars test framework to implement a CI/CD methodology to developing my website code any further.

Leave a Reply

All fields marked with an asterisk (*) are required