Informationen i dette indlæg kan være forældet da det omhandler Angular 6. I skrivende stund er Angular 13 på trapperne.
Det fede Angular er at det er så ufattelig nemt at komme igang. Nedenstående er mine noter fra denne Angular 6 tutorial som jeg varmt kan anbefale.:
Indholdsfortegnelse
Opret projekt
Først skal vi have fat i Angulars cli (command line interface):
1 |
# npm install -g @angular/cli |
Programmet vi bruger til at installere med (npm
) er en del af nodejs
. Parameteren -g
tilføjer vi for at installere cli’et globalt. Dvs. vi bagefter kan bruge det overalt på computeren.
Efter installationen kan vi skrive Angular cli kommandoer ved at prefikse dem med ng
. Det næste vi skal gøre er nemlig at oprette et nyt Angular projekt. Bemærk at du fra nu af benytter de enkelte kommandoer som almindelig bruger:
1 |
$ ng new mitProjekt --skip-git --routing --style scss --prefix mp |
Erstat “mitProjekt” med dit eget projektnavn. Bemærk at navnet lige pt. hverken kan indeholde tal, bindestreg eller understreg. Du kan også tilføje --style scss
(default er almindelig css). Det er en fordel hvis du f.eks. tilføjer Bootstrap da det gør det nemt at integrere og overskrive de default værdier Bootstrap kommer med. Jeg tilføjer --routing
fordi mit projekt har sider med forskellige adresser (url’er) og denne parameter hjælper med at oprette en routnings-fil. Flaget --prefix
hjælper til at undgå navnekollisioner med andres moduler hvis du deler dit projekt.
Jeg har desuden tilføjet --skip-git
ganske enkelt fordi jeg bruger subversion. I know. It’s sooo old 😉
Se mere info her: https://www.amadousall.com/my-favourite-angular-cli-commands-and-options/
Følgende kommando kompilerer og åbner projektet i din browser, forudsat at du befinder dig i projektets mappe :
1 2 |
$ cd mitProjekt/ $ ng serve -o |
Projektets filstruktur
1 2 3 4 5 6 7 8 9 10 11 |
> e2e/ > node_modules/ > src/ .editorconfig .gitignore angular.json package-lock.json package.json README.md tsconfig.json tslint.json |
package.json
indeholder navn og versionsnummer for dit projekts afhængigheder,
I src/
folderen ligger det medfølgende eksempelprojekt og det er her du arbejder med dit eget projekt.
1 2 3 4 5 6 7 8 9 |
> src/ > app > assets > environments browserslist favicon.ico index.html karma.conf.js main.ts |
Under app/
placeres alle komponenter og det er her du kommer til arbejde mest med projektets kode.
Opret en menu (sidebar)
Et ny komponent oprettes med denne kommando:
1 |
$ ng generate component sidebar |
… eller via den forkortede:
1 |
$ ng g c sidebar |
… og “sidebar” er komponentets valgfri navn. Det skal naturligvis være et sigende navn der fortæller noget om hvad komponentets opgave er.
Kommandoen opretter app/sidebar
med 4 filer og føjer komponentet til app/app.module.ts
under ‘declarations’ sektionen.
Vi kan nu definerer et simpelt 2-kolonne design for vores app, ved at skrive følgende indhold i app/app.component.html
som er vores basiskomponent:
1 2 3 4 5 6 7 |
<div id="container"> <app-sidebar></app-sidebar> <div id="content"> <router-outlet></router-outlet> </div> </div> |
Komponentet “app-sidebar” er nu den første kolonne. Den er navngivet med værdien i “selector” som er defineret i app/sidebar/sidebar.component.ts
, og dermed kan vi inkludere vores sidebar komponent i ethvert andet komponent med html tag’et <app-sidebar>
. Og det er det vi lige har gjort, nemlig ved at tilføje <app-sidebar>
i “app” komponentet.
Den anden kolonne er næsten selvforklarende. Det er her app’ens indhold (content) bliver vist alt efter hvor i app’en brugeren befinder sig. Derfor har vi her tilføjet <router-outlet>
som blev generet da vi tilføjede --routing
parameteren ved oprettelsen af projektet.
Hvis vi på nuværende tidspunkt kompilerer og åbner i browseren, vil du blot se “sidebar works!”. Her vises nemlig indholdet af app/sidebar/sidebar.component.html
. Anden kolonne er endnu ikke synlig, da der intet indhold er endnu.
Nu åbner vi app/sidebar/sidebar.component.html
:
1 |
<p>Sidebar works!</p> |
… og udskifter indholdet med:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
<nav> <ul> <li> <a routerLink=""> <i class="material-icons">supervised_users_circle</i> </a> </li> <li> <a routerLink=""> <i class="material-icons">message</i> </a> </li> </ul> </nav> |
Bemærk at vi i stedet for den kendte “href” attribut, bruger “routerLink”.
Vores sidebar menu skal også bruge nogle ikoner. Den tekst du ser i <i> html tag’et, vil ikke blive vist. Det er blot navnet på det ikon vi ønsker at benytte. I dette eksempel bruger vi Google’s “Material Icons” ikonpakke, som vi importerer ved at linke til dem i index.html. Følgende linie indsættes i head-sektionen:
1 |
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> |
Nu skal vi fortælle vores router hvor de enkelte links skal føre hen. Derfor åbnes app/app-routing.module.ts
:
1 2 3 4 5 6 7 8 9 10 |
import { NgModule } from '@angular/core'; import { Routes, RouterModule } from '@angular/router'; const routes: Routes = []; @NgModule({ imports: [RouterModule.forRoot(routes)], exports: [RouterModule] }) export class AppRoutingModule { } |
Hertil skal vi importerer alle de komponenter vi vil kunne rute til…
Vi starter derfor med at tilføje følgende på linie 3:
1 2 3 |
import { UsersComponent } from './users/users.component'; import { DetailsComponent } from './details/details.component'; import { PostsComponent } from './posts/posts.component'; |
Dette er kun for eksemplets skyld, da vi ikke har oprettet disse komponenter.
Nu associerer vi hvert komponent med en url. Dette gøres ved at udskifte linien…
1 |
const routes: Routes = []; |
…med følgende:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
const routes: Routes = [ { path: '', component: UsersComponent }, { path: 'details/:id', component: DetailsComponent }, { path: 'posts', component: PostsComponent }, ]; |
Dermed har vi fortalt at UsersComponent er vores standard indhold når intet andet er defineret. Det er altså vores forside. For DetailsComponent ønsker vi url’en /details
og har samtidig angivet at der her skal være mulighed for at medsende en værdi, nemlig et id
. Og for PostsComponent skal url’en slet og ret være /posts
.
Opret en liste og hent data
Nu skal vi oprette en service til visning af vores brugerliste i UsersComponent. Men lad os først oprette UsersComponent:
1 |
$ ng g c users |
… og nu en service til at hente data med (data er et valgfrit navn):
1 |
$ ng generate service data |
.. eller blot:
1 |
$ ng g s data |
Dette skaber ikke en ny mappe, men placerer blot service filerne under app/
.
1 2 |
app/data.service.spec.ts app/data.service.ts |
Filen data.service.ts
ser således ud:
1 2 3 4 5 6 7 8 9 |
import { Injectable } from '@angular/core'; @Injectable({ providedIn: 'root' }) export class DataService { constructor() { } } |
En service består af kode som kan genbruges hvor som helst. Den bekymrer sig ikke om design, men leverer blot noget funktionalitet eller data.
I dette tilfælde vil vi hente brugerdata fra et api. Vi vælger i dette tilfælde at hente fra en online service der stiller eksempeldata til rådighed: jsonplaceholder.typicode.com
I linie 2 tilføjer vi:
1 |
import { HttpClient } from '@angular/common/http'; |
… vi føjer HttpClient
til constructor
metoden og indsætter en ny funktion der henter brugerdata:
1 2 3 4 |
constructor(private http: HttpClient) { } getUsers() { return this.http.get('https://jsonplaceholder.typicode.com/users') } |
Nu importerer vi HttpClient modulet i app.module.ts
:
1 |
import { HttpClientModule } from '@angular/common/http'; |
… og i samme fil, føjer vi den til imports:
1 2 3 4 5 |
imports: [ BrowserModule, AppRoutingModule, HttpClientModule, // <- Tilføj her ], |
Vores nye data service skal nu importeres til det komponent vi skal bruge det i. Vi åbner derfor user.component.ts
og angiver en import linie:
1 |
import { DataService } from '../data.service'; |
Samme sted importerer vi rxjs som basalt set er den der indeholder de data vi henter:
1 |
import { Observable } from 'rxjs'; |
… og vi føjer lidt til vores export class
sektion:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
export class UsersComponent implements OnInit { users$: Object; constructor(private data: DataService) { } ngOnInit() { this.data.getUsers().subscribe( data => this.users$ = data ); } } |
Nu skal vi vise listen og det foregår i users.component.html:
1 2 3 4 5 6 7 8 9 10 11 12 |
<h1>Users</h1> <ul> <li *ngFor="let user of users$"> <a routerLink="/details/{{user.id}}">{{ user.name }}</a> <ul> <li>{{ user.email }}</li> <li><a href="http://{{ user.website }}">{{ user.website }}</a></li> </ul> </li> </ul> |
Nu genererer vi vores details komponent:
1 |
# ng g c details |
… og føjer et par ekstra funktioner til app/data.service.ts
nemlig getUser og getPosts:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
constructor(private http: HttpClient) { } getUsers() { return this.http.get('https://jsonplaceholder.typicode.com/users') } getUser(userId) { return this.http.get('https://jsonplaceholder.typicode.com/users'+userId) } getPosts() { return this.http.get('https://jsonplaceholder.typicode.com/posts') } |
Vores details komponent skal også benytte brugerdata og vi importerer derfor også vores dataservice og rxjs her. Åben details.component.ts
:
1 2 |
import { DataService } from '../data.service'; import { Observable } from 'rxjs'; |
Denne linie skal også tilføjes, og den giver mening om lidt:
1 |
import { ActivatedRoute } from '@angular/router'; |
… og vi føjer lidt til vores export class
sektion:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
export class DetailsComponent implements OnInit { user$: Object; constructor(private route: ActivatedRoute, private data: DataService) { this.route.params.subscribe( params => this.user$ = params.id ); } ngOnInit() { this.data.getUser(this.user$).subscribe( data => this.user$ = data ); } } |
Det nye i ovenstående er linie 6. Her henviser vi med params.id
til den parameter i url’en vi navngav id
i rutefilen. Og vi kan nu se at ActivatedRoute
giver os adgang til den aktuelle værdi af den parameter. Navnet alene er næsten selvforklarende: Activated route = Den aktive rute, dvs. den url og det komponent der pt er aktivt og det er i dette tilfælde “details”. Du kan læse mere om ActivatedRoute her: Angular – ActivatedRoute
I filen details.component.html
indsætter vi nu følgende:
1 2 3 4 5 6 7 |
<h1>{{ user$.name }}</h1> <ul> <li><strong>Username:</strong> {{ user$.username }}</li> <li><strong>Email:</strong> {{ user$.email }}</li> <li><strong>Phone:</strong> {{ user$.phone }}</li> </ul> |
Vi skal også vise beskeder (posts):
1 |
$ ng g c posts |
… og vores posts.component.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 |
import { Component, OnInit } from '@angular/core'; import { DataService } from '../data.service'; import { Observable } from 'rxjs'; @Component({ selector: 'app-posts', templateUrl: './posts.component.html', styleUrls: ['./posts.component.scss'] }) export class PostsComponent implements OnInit { posts$: Object; constructor(private data: DataService) { } ngOnInit() { this.data.getPosts().subscribe( data => this.posts$ = data ); } } |
… og posts.component.html
:
1 2 3 4 5 6 7 8 9 |
<h1>Posts</h1> <ul> <li *ngFor="let post of posts$"> <a routerLink="">{{ post.title }}</a> <p>{{ post.body }}</p> </li> </ul> |
Marker den aktuelle side i menuen
Nu kunne det være smart at markere hvilket menupunkt der pt er aktivt i vores menu (sidebar). Vi åbner derfor sidebar.component.ts
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
import { Component, OnInit } from '@angular/core'; import { Router, NavigationEnd } from '@angular/router'; @Component({ selector: 'app-sidebar', templateUrl: './sidebar.compotent.html', styleUrls: ['./sidebar.component.css'] }); export class SidebarComponent implements OnInit { currentUrl: string; constructor(private router: Router) { router.events.subscribe((_: NavigationEnd) => this.currentUrl = _.url); } ngOnInit() {} } |
I vores constructor
abonnerer vi nu på event’et NavigationEnd. Dvs. når en navigation/sideskift er slut, får vi her besked om det, og får den aktuelle url.
I sidebar.component.html redigeres de eksisterende <a>
tags:
1 |
<a routerLink="" [class.activated]="currentUrl == '/'"> |
… og …
1 |
<a routerLink="" [class.activated]="currentUrl == '/posts'"> |
… osv…
Dette betyder at når et link er aktivt vil css klassen ‘activated’ bliver tilføjet, således at <a>
tagget i browseren ser således ud:
1 |
<a routerLink="" class="activated"> |
Animationer
Det næste vi skal prøve er at animere html-elementer. Først installerer vi Angulars animate modul:
1 |
# npm install @angular/animations@latest --save |
Modulet skal nu importeres til app.module.ts
1 |
import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; |
… og vi føjer det samtidig til imports:
1 2 3 4 5 6 7 |
@NgModule({ ... imports: [ ... BrowserAnimationsModule ], }) |
I users.components.ts
:
1 |
import { trigger,style,transition,animate,keyframes,query,stagger } from '@angular/animations'; |
… og i @Component
sektionen:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
@Component({ selector: 'app-users', templateUrl: './users.component.html', styleUrls: ['./users.component.scss'], // Tilføj dette: animations: [ trigger('listStagger', [ transition('* <=> *', [ query( ':enter', [ style({ opacity: 0, transform: 'translateY(-15px)' }), stagger( '50ms', animate( '550ms ease-out', style({ opacity: 1, transform: 'translateY(0px)' }) ) ) ], { optional: true } ), query(':leave', animate('50ms', style({ opacity: 0 })), { optional: true }) ]) ]) ] }) |
I users.component.html
, mangler vi nu bare at henvise til vores animation i <ul>
tag’et:
1 |
<ul [@listStagger]="users$"> |
Byg dit projekt
Når du er klar til at offentliggøre dit projekt skal det bygges og evt. komprimeres, dvs:
Oversæt til javascript:
1 |
$ ng build |
…. eller ….
Oversæt til javascript og komprimér:
1 |
$ ng build --prod |
Hvis der er nogen uhensigtsmæssigheder i dit projekt, vil byggeprocessen fortælle om det:
ERROR in src/app/details/details.component.html(1,7): Property type ‘name’ does not exist on type ‘Object’
Her har vi glemt at pakke vores visning af en brugers information ind i en ngIf, så det gør vi lige:
I filen details.component.html
indsætter vi nu følgende:
1 2 3 4 5 6 7 8 9 |
<div *ngIf="user$"> <h1>{{ user$.name }}</h1> <ul> <li><strong>Username:</strong> {{ user$.username }}</li> <li><strong>Email:</strong> {{ user$.email }}</li> <li><strong>Phone:</strong> {{ user$.phone }}</li> </ul> </div> |
Kommandoen build
opretter en dist mappe, hvis den ikke eksisterer, og placerer der færdige projekt heri. Bemærk forskellen mellem at bygge med eller uden --prod
parameteren. Projekter fylder mange mb uden og under 1 mb med. Det er komprimeringen der gør hele forskellen.
Tilføj Bootstrap
Hvis du vil lave responsive design… dvs. en hjemmeside/app der skalerer flot på alle skærmstørrelser, er det ikke nemt at komme uden om Bootstrap. Her får du det hele forærende og en nem måde at arbejde på:
1 |
$ ng add @ng-bootstrap/schematics |
I app.module.ts
:
1 2 3 4 5 6 7 8 9 10 |
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @NgModule({ ...... imports: [ NgbModule.forRoot() ] ...... }) |
Hvis du har en ng serve -o
kørende, skal du genstarte den for at få adgang til at benytte Bootstrap styles.
… og i hver eneste xx.component.ts hvor du vil benytte Boostrap fremover:
1 |
import { NgbModule } from '@ng-bootstrap/ng-bootstrap'; |
Valg af editor/IDE
Jeg har altid brugt Vim som editor. Den er dejlig simpel og nem at anvende. Men jeg må også tilstå at jeg aldrig har været en haj til at bruge den, og har således ikke rigtig udnyttet alle de muligheder der gør redigering super effektivt. Det der med at huske en masse tastaturgenveje mens jeg sidder og programmerer – det du’r bare ikke for mig. Alligevel har jeg stædigt brugt Vim de sidste 10-15 år.
Men med Angular 6 føler jeg et behov for at prøve noget andet. En editor med GUI og hele pivtøjet. Så jeg har været på Google for at se hvad andre bruger. Webstorm lader til at være ret udbredt, men de er heller ikke kede af det sådan rent prismæssigt. Jeg lod mig derfor inspirere af dette svar på Stackoverflow… og slugte verdens største kamel. Jeg har installeret Visual Studio Code, som er en gratis editor fra [dunkel musik her] Microsoft!!! Men den er rent faktisk open source… og folk anbefaler den, så den er installeret, og foreløbig er jeg glad 🙂
Du kan se her, hvordan du installerer Visual Studio Code på nærmest enhver platform og distro: Running Visual Studio Code on Linux