# Motivering för deklarativt GUI
## Inkapsling
En angular App består av ett träd av Angular-Components.
https://angular.io/api/core/Component
När man använder externa bibliotek, har duplicerad kod eller HTML så är det god praxis att kapsla in detta i en komponent anpassad för domänens utseende eller förutsättningar.
Delade komponenter ska läggas i en modul och mapp vid namn "Shared" vilket beskrivs i Angulars styleguide:
* Do create a feature module named SharedModule in a shared folder; for example, app/shared/shared.module.ts defines SharedModule.
* Do declare components, directives, and pipes in a shared module when those items will be re-used and referenced by the components declared in other feature modules.
* Try to be DRY (Don't Repeat Yourself). Avoid being so DRY that you sacrifice readability.
https://angular.io/guide/styleguide#shared-feature-modul
Komponenten kan då ändras eller bytas ut utan att påverka den kringliggande koden. Om ny funktionalitet behövs eller om en bugg skulle hittas så behöver denna oftast endast rättas i komponenten och kommer då slå igenom på alla ställen som komponenten används.
Som standard har Angular att egna komponenter ska prefixas med 'App'.
**Exempel**
Följande tabell skapas med det vanlig förekommande GUI-biblioteket Boostrap ([https://getbootstrap.com/docs/4.0/content/tables/](https://getbootstrap.com/docs/4.0/content/tables/)) och wrappas i komponenten `<app-table>`:
```js
<table class="table table-sm table-dark">
<thead>
<tr>
<th *ngFor="let col of columns" scope="col">
{{col.header}}
</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of rows">
<td *ngFor="let col of columns">
{{row[col.field]}}
</td>
</tr>
</table>
...
```
Vår tabell kan nu återanvändas på följande sätt:
```js
<app-table [columns]="columns" [rows]="rows">
```
T.ex. med följande input:
```js
const columns = [
{ field: 'code', header: 'Code' },
{ field: 'name', header: 'Name' },
{ field: 'category', header: 'Category' },
{ field: 'quantity', header: 'Quantity' }
];
const rows = [
{ code: 'f230fh0g3', name: 'Bamboo Watch', category: 'Accessories', quantity: 24 },
{ code: 'nvklal433', name: 'Black Watch', category: 'Accessories', quantity: 61 }
];
```
Genom Angulars inbyggda change detection för template-bindings så kan ändringar i input fortfarande ske i efterhand och plockas upp av komponenten som ritas om i realtid.
**Exempel**
Kraven på applikationen ändras och tabellerna behöver bytas från Bootstrap till PrimeNG.
Implementationen av app-table kan då ändras till PrimeNG dynamic table (https://primefaces.org/primeng/showcase/#/table/dynamic):
```html
<p-table [columns]="columns" [value]="rows">
<ng-template pTemplate="header" let-columns>
<tr>
<th *ngFor="let col of columns">
{{col.header}}
</th>
</tr>
</ng-template>
<ng-template pTemplate="body" let-rowData let-columns="columns">
<tr>
<td *ngFor="let col of columns">
{{rowData[col.field]}}
</td>
</tr>
</ng-template>
</p-table>
```
Interface:et till "app-table" är fortfarande oförändrat.
```js
<app-table [columns]="columns" [rows]="rows">
```
Fördelen blir mer markant om ytterligare funktionalitet behöver läggas till för tabeller, t.ex. om tabellerna behöver utökas med att inkludera paginering och sökfunktion. Detta kan medföra att ytterligare parametrar och flaggor läggs till, men överlag utan att bryta befintliga beroenden på tabellen.
Detta uppfyller kraven på "Teknisk flexibilitet" som finns beskrivet i dokumentet "GENSAM - SAD - Portal - FE"
* Undvik direkta beroenden på tekniska komponenter för att begränsa påverkan när komponenten uppdateras eller byts ut.
* Kapsla in eller konvertera komponentspecifika parametrar och returvärden så att all kommunikation sker i termer av generella datatyper.
* Om möjligt, basera referenser på en stabil standard, eller kapsla in de proprietära anropen bakom en egentillverkad fasad.
## Option object pattern
För att reducera antal parametrar som behövs för att skapa en komponent och kapsulera interface ändringar så har vi i många komponenter grupperat dessa till ett option-objekt, vilket är ett vanligt förkommande design-pattern i JavaScript.
https://travishorn.com/using-the-options-object-js-pattern-221f083aadbc
Typsäkerhet säkerställs även på parametrar genom ett interface.
Vårt exempel skulle då kunna skrivas om till följande:
```html
<app-table [options]="tableOptions">
```
```js
const tableOptions: TableOptions = {
rows: rows,
items: items,
pagination: true,
searchBar: true
}
```
## Dynamiska komponenter
Förutom primitiva datatyper så finnas det ibland behov av att skapa komponenter dynamiskt i Angular, t.ex. för att lägga till knappar eller andra komponenter i en tabell.
För att göra detta så finns främst två metoder i Angular.
Den första är genom "ComponentFactoryResolver":
https://angular.io/guide/dynamic-component-loader
Den andra är genom att använda NgSwitch:
https://angular.io/api/common/NgSwitch
Av dessa har vi valt den senare lösningen.
Detta är implementerat i en klass vi har döpt till `<app-dynamic-item>` som har ett gemensamt option-objekt för vanligt förkommande komponenter.
```html
<ng-container [ngSwitch]="item.type">
<app-table *ngSwitchCase="ItemType.TABLE" [item]="item"></app-table>
<app-button *ngSwitchCase="ItemType.BUTTON" [item]="item"></app-button>
...
</ng-container>
```
Och används på följande vis:
```html
<app-dynamic-item [item]="tableItem">
```
```ts
const tableItem: DynamicItem = {
type: ItemType.TABLE,
headers: headers,
rows: rows
}
```
Detta gör det enkelt att lägga till stöd för dynamiskt innehåll i komponenter så som `<app-table>`.
```html
<!-- Modifiering av pTemplate="body" -->
<ng-template *ngIf="isDynamicItem(row, col)">
<app-dynamic-item [item]="rowData[col.field]"></app-dynamic-item>
</ng-template>
<ng-template *ngIf="!isDynamicItem(row, col)">
{{rowData[col.field]}}
</ng-template>
```
Knappar och andra komponenter kan nu läggas till i tabellen på följande vis:
```ts
const columns = [
{ field: 'name', header: 'Name' },
{ field: 'button' },
];
const rows = [
{
name: 'Bamboo Watch',
button: {
type: ItemType.BUTTON,
text: 'Select row',
click: () => alert('You clicked me')
}
}
];
```
## Dynamiskt innehåll på sidor
Det kan ibland vara en tydlig fördel att separera innehållet (datan) från beteendet och presentationen. Innehållet kan då enkelt ändras eller bytas ut och presentationen kan återanvändas för att snabbt skapa nytt innehåll.
Detta kallas ofta för Data-driven programming:
https://en.wikipedia.org/wiki/Data-driven_programming
Ett flertal populära ramverk och verktyg har även ett liknande flöde, t.ex:
* Gitlab CI/CD
https://docs.gitlab.com/ee/ci/quick_start/
* Serverless Framework
https://www.serverless.com/framework/docs/providers/aws/guide/intro/
* Declarative Pipline i Jenkins
https://www.jenkins.io/doc/book/pipeline/syntax/
Många av våra sidor följer ett likartat mönster och beteende men med olika uppsättningar innehåll och data. Vi har därför använt samma princip som för tabeller för att skapa en komponent av typen `<app-dynamic-page>` som tar en lista med `<app-dynamic-item>` för att skapa upp innehållet i en sida dynamiskt.
Detta använder vi särkilt för våra formulär, som i följande exempel:
```html
<app-dynamic-page [page]="page"></app-dynamic-page>
```
```ts
const page: DynamicPage = {
layout: PageLayout.FULL_PAGE,
items: [
{
type: ItemType.TEXT,
title: 'Uppgifter',
text: 'Vänlig fyll i uppgifterna nedan',
},
{ type: ItemType.INPUT, title: 'Namn', id: 'namn', required: true },
{
type: ItemType.RADIO, title: 'Är prov från primärodling?', id: 'franPrimarOdling', options: [
'Ja', 'Nej', 'Vet ej'
],
},
{ type: ItemType.UPLOAD, title: 'Ladda upp provfil', id: 'provfil', accept: IONTORRENT_FILE_TYPES },
{ type: ItemType.BUTTON, text: 'Spara formulär', submit: true },
]
};
```
DynamicPage har även några extra valbara flaggor, t.ex. layout för att välja mellan de vanligaste förkommande layouterna på sidan. I detta fallet är de fördefinerade layouterna definerade enligt normal Boostrap-kod i DynamicPage, vilket gör det lätt att göra globala layout-ändringar om ny design skulle krävas i framtiden och är samtidigt lätt att ändra för någon som är bekant med Bootstrap.
Det finns även options för "onSubmit"-callback som ger ett key/value-objekt med de värden som är ifyllda i formuläret och "formValues" tar emot ett key/value-objekt för att ladda in värden i ett existerande formulär baserat på dess id, då detta är ett vanligt användarfall samt att undvika att utvecklare implementerar olika lösingar som måste underhållas separat.
På samma sätt så har DynamicPage även en option för att snabbt skapa en sidmeny utifrån parametern "sideItems".
Fördelar:
* Väldigt flexibelt och modulärt då innehåll enkelt kan bytas ut och återanvändas.
* Lätt att skapa nytt innehåll som följer samma utseende och mönster.
* Lätt för nya utvecklare att göra ändringar i befintligt innehåll då sidor följer en tydlig struktur.
* På många sätt uppfyller komposition-över-arv.
Nackdelar
* Något okonventionellt.
* Kan kräva mer kod för att hantera specialfall.
* Kan vara svårare för nya utvecklare att lägga till ny funktionalitet innan de har läst på om DynamicItem. Bortsätt från att en case-sats behöver läggas till i DynamicItem så är dock arbetsbördan och tidsåtgången i stort sett oförändrad. Upplärningen av nya utvecklare är också en avvägning som måste göras vid alla former av inkapslingar.
* Interface till DynamicItem kan bli relativt stort om man är oförsiktig. Eventuellt kan då delar av DynamicItem behöva brytas ut till mindre specialanpassade interface.
## Valfrihet
Utvecklare bör uppmuntras att undvika specialfall om det går, men om situationen ändå kräver det så kan App-componenter fortfarande användas på egen hand med eller utan `app-dynamic-page` och i kombination med standard HTML-element.
Exemplet ovan kan t.ex. skrivas om i ren HTML:
```html
<app-dynamic-page [page]="{layout: PageLayout.FULL_PAGE}">
<app-text [item]="{
title: 'Uppgifter',
text: 'Vänlig fyll i uppgifterna nedan'
}"></app-text>
<app-input [item]="{title: 'Namn', id: 'namn', required: true}"></app-input>
<app-radio [item]="{
title: 'Är prov från primärodling?',
id: 'franPrimarOdling',
options: [ 'Ja', 'Nej', 'Vet ej']
}"></app-radio>
...
</app-dynamic-page>
```
Detta kan dock inte i nuläget dra nytta flera fördelar som DynamicItem ger, så som automatisk inladdning av formulär och populering av hjälptexter, då dessa sker i init-metoden för DynamicPage och DynamicItem.
## Testning
Våra shared App-komponenter kan enhetstestas normalt. I många fall bygger dessa komponenter på inkapslade tredjepartskomponenter (t.ex. Bootstrap, PrimeNG) som i många avseenden redan är beprövade, dokumenterade och testade. Det option-objekt som används har för de flesta komponenter ingen logik kopplad till sig utan är endast ett sätt för att gruppera parametrar.
I de fall då callbacks används, t.ex, "`click = () => {}`" för en BUTTON-komponent så binds dessa oftast direkt till motsvarande callback för native-HTML. Några undantag av egna callbacks-existerar vilka ska enhetstestas när tidramen i projektet tillåter.
Ett betydande utantag är DynamicPage som har logik för att spara och fylla i formulär utifrån det gemensamma interface som DynamicItem ger. Även hjälpttexter läggs automatiskt på DynamicItem i DynamicPage, och båda har även en init-metod för default-värden. Detta bör också enhetstestas när tillfälle ges, men tills vidare så borde majoriteten av buggar fångas upp av de befintliga E2E-tester som finns.
## Förvaltning
Då kärnan i det deklarativa är väldigt enkelt bör även förvaltningen bli lika enkel som en motsvarande standard-applikation. Då arkitekturen bygger på flexibilitet så kommer små förändringar vara väldigt enkla att göra. Eftersom det till viss del inte är standard-Angular krävs det tydlig dokumentation, men tydlig dokumentation är ett krav oavsett tillvägagångssätt.
## Sammanfattning
Vi anser att webbsidan blir väldigt modulär, flexibel, och lättarbetad om följande arkitektur efterföljs.
Ett krav detta ställer på arkitekturen är att nya utvecklare inom rimlig tid ska bli produktiva. Detta vill vi addressera ytterligare i form av dokumentation och utökad enhetstestning. Detta kommer dock alltid att vara en väldigt subjektiv bedöming oavsett vilken arkitektur man väljer och vi tar gärna emot övriga förslag på hur detta kan förbättras utöver de åtgärder vi redan har vidtagit eller föreslagit.