Tutoriels Backend
Services avec Angular
Nous allons créer nos premiers services avec Angular CLI et Typescript.
Ce que nous allons faire
- Avant de Commencer
Qu'est ce qu'un service
- Angular et les services
Comment Angular gère la notion de services.
- Création de notre projet Angular
Nous utiliserons un projet existant contenant les fonctionnalités essentielles.
Le projet a été généré avec Angular CLI.
Il utilise le Routing et le Lazy Loading.
Il intègre le Framework CSS Bootstrap.
- Adapter notre application
Nous créerons une page Services pour pouvoir effectuer tous nos tests
- Création d'un premier service
Utiliser Angular CLI pour créer ce service
- Effectuer les Tests
Réaliser les tests unitaires.
- Code source
Le code complet du projet sur github.
Qu'est ce qu'un service ?
Un service est un moyen de partager des informations entre des classes qui ne se connaissent pas.
Création du projet Angular
Pour pouvoir continuer ce tutoriel nous devons bien evidemment disposer de certains éléments
- Node.js : La plateforme javascript
- Git : Le logiciel de gestion de versions.
- Angular CLI : L'outil fourni par Angular.
- Visual Studio code : Un éditeur de code.
Vous pouvez consulter le tutoriel suivant qui vous explique en détails comment faire
Nous allons utiliser un projet existant dont les caractéristiques sont
- Genéré avec Angular CLI
- Routing
- Lazy loading
- Utilisation du Framework CSS Bootstrap
# Créez un répertoire demo (le nom est ici arbitraire)
mkdir demo
# Allez dans ce répertoire
cd demo
# Récupérez le code source sur votre poste de travail
git clone https://github.com/ganatan/angular-react-bootstrap.git
# Allez dans le répertoire qui a été créé
cd angular-react-bootstrap
cd angular
# Exécutez l'installation des dépendances (ou librairies)
npm install
# Exécutez le programme
npm run start
# Vérifiez son fonctionnement en lançant dans votre navigateur la commande
http://localhost:4200/
Notre prototype contient la partie graphique finale.
Pour avoir plus de détail vous pouvez consulter le tutoriel sur Angular et Bootstrap
Prototype avec Bootstrap et Angular
Nouvelle fonctionnalité
Nous allons rajouter une page dédiée aux services dans notre projet.
C'est dans cette page que nous ferons tous nos tests et modifications de services.
Tout d'abord créons la nouvelle page avec les commandes Angular CLI correspondantes.
ng generate component modules/application/services
ng generate module modules/application/services --routing --module=app
{
path: 'services',
loadChildren: () => import('./modules/application/services/services.module')
.then(mod => mod.ServicesModule)
},
import { NgModule } from '@angular/core';
import { Routes, RouterModule } from '@angular/router';
import { ServicesComponent } from './services.component';
const routes: Routes = [
{ path: '', component: ServicesComponent },
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class ServicesRoutingModule { }
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ServicesComponent } from './services.component';
import { ServicesRoutingModule } from './services-routing.module';
@NgModule({
imports: [
CommonModule,
ServicesRoutingModule
],
exports: [
ServicesComponent
],
declarations: [
ServicesComponent
],
providers: [
],
})
export class ServicesModule { }
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppComponent } from './app.component';
import { HomeComponent } from './modules/general/home/home.component';
import { NotFoundComponent } from './modules/general/not-found/not-found.component';
import { AppRoutingModule } from './app-routing.module';
@NgModule({
declarations: [
AppComponent,
HomeComponent,
NotFoundComponent,
],
imports: [
BrowserModule,
AppRoutingModule,
],
providers: [],
bootstrap: [AppComponent]
})
export class AppModule { }
Premier Service
Toutes les modifications se situeront donc dans le répertoire
app/modules/application/services
Il suffira de copier ce répertoire dans votre projet pour le tester à votre guise.
Nous allons pouvoir créer notre premier service.
Utilisons pour cela la commande Angular CLI suivante.
# Commande Angular CLI ng generate
# Création du répertoire song
# Création du service song dans le répertoire song
ng generate service modules/application/services/song/song
Liste simple
Voila les fichiers
export class Song {
color: string;
date: string;
artist: string;
title: string;
}
<div class="row">
<div class="text-center col">
<h1 class="h3">Feature : Components</h1>
<hr>
</div>
</div>
<div class="row pb-4">
<div class="col-12 col-sm-12 col-md-8 col-lg-8 col-xl-8">
<table class="table table-hover table-striped table-bordered table-responsive-md table-sm">
<thead>
<tr>
<th style="text-align:center;">Date</th>
<th style="text-align:center;">Artist</th>
<th style="cursor: pointer;text-align:center;">Title</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let song of songs; let i=index" class="table-{{ song.class }}">
<td style="cursor: pointer;text-align:center">{{ song.date }}</td>
<td style="cursor: pointer;text-align:center">{{ song.artist }}</td>
<td style="cursor: pointer;text-align:center">{{ song.title }}</td>
</tr>
</tbody>
</table>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Song } from './song/song';
import { SongService } from './song/song.service';
@Component({
selector: 'app-services',
templateUrl: './services.component.html',
styleUrls: ['./services.component.css']
})
export class ServicesComponent implements OnInit {
songs: Song[];
constructor(private songService: SongService) {
}
ngOnInit() {
this.getSongs();
}
getSongs(): void {
this.songs =
[
{ color: 'light', date: '13/01/2017', artist: 'Ed Sheeran', title: 'Shape of You' },
{ color: 'primary', date: '28/04/2017', artist: 'Luis Fonsi feat. Daddy Yankee', title: 'Despacito' },
{ color: 'light', date: '28/07/2017', artist: 'Niska', title: 'Réseaux' },
{ color: 'info', date: '13/10/2017', artist: 'Kalash feat. Damso', title: 'Mwaka Moon' },
{ color: 'light', date: '08/12/2017', artist: 'Booba', title: 'Petite fille' },
{ color: 'primary', date: '15/12/2017', artist: 'Johnny Hallyday', title: 'Je te promets' },
{ color: 'light', date: '22/12/2017', artist: 'Ed Sheeran', title: 'Perfect 4' },
{ color: 'info', date: '19/01/2018', artist: 'Camila Cabello feat. Young Thug', title: 'Havana' },
{ color: 'light', date: '02/02/2018', artist: 'Vald', title: 'Désaccordé' },
{ color: 'primary', date: '23/03/2018', artist: 'Lartiste feat. Caroliina', title: 'Mafiosa' },
];
}
}
Liste avec service
Nous allons maintenant procéder différemment.
Nous allons séparer le traitement des données et celui de l'affichage.
Le service s'occupera des données.
Modifions le service.
Appelons dans notre page.
import { Component, OnInit } from '@angular/core';
import { Song } from './song/song';
import { SongService } from './song/song.service';
@Component({
selector: 'app-services',
templateUrl: './services.component.html',
styleUrls: ['./services.component.css']
})
export class ServicesComponent implements OnInit {
songs: Song[];
constructor(private songService: SongService) {
}
ngOnInit() {
this.getSongs();
}
getSongs(): void {
this.songs = this.songService.getSongs();
}
}
import { Injectable } from '@angular/core';
import { Song } from './song';
import { SONGS } from './mock-songs';
@Injectable({
providedIn: 'root'
})
export class SongService {
constructor() { }
getSongs(): Song[] {
return SONGS;
}
}
import { Song } from './song';
export const SONGS: Song[] = [
{ color: 'light', date: '13/01/2017', artist: 'Ed Sheeran', title: 'Shape of You' },
{ color: 'primary', date: '28/04/2017', artist: 'Luis Fonsi feat. Daddy Yankee', title: 'Despacito' },
{ color: 'light', date: '28/07/2017', artist: 'Niska', title: 'Réseaux' },
{ color: 'info', date: '13/10/2017', artist: 'Kalash feat. Damso', title: 'Mwaka Moon' },
{ color: 'light', date: '08/12/2017', artist: 'Booba', title: 'Petite fille' },
{ color: 'primary', date: '15/12/2017', artist: 'Johnny Hallyday', title: 'Je te promets' },
{ color: 'light', date: '22/12/2017', artist: 'Ed Sheeran', title: 'Perfect 4' },
{ color: 'info', date: '19/01/2018', artist: 'Camila Cabello feat. Young Thug', title: 'Havana' },
{ color: 'light', date: '02/02/2018', artist: 'Vald', title: 'Désaccordé' },
{ color: 'primary', date: '23/03/2018', artist: 'Lartiste feat. Caroliina', title: 'Mafiosa' },
];
Services plus complexes
Améliorons notre service et son utilisation.
export class Song {
link: string;
top: string;
artist: string;
title: string;
}
import { Injectable } from '@angular/core';
import { Song } from './song';
import { SONGS } from './mock-songs';
@Injectable({
providedIn: 'root'
})
export class SongService {
song: Song[];
constructor() { }
getSongs(year: number): Song[] {
this.song = SONGS[year - 1].items;
return this.song;
}
}
export const SONGS: any[] =
[
{
year: '2018',
items: [
{ link: 'S6baf8BqKDI', top: '1', artist: 'Lartiste featuring Caroliina', title: 'Mafiosa' },
{ link: 'kutk2XHEZNU', top: '2', artist: 'Vald', title: 'Désaccordé' },
{ link: 'iPGgnzc34tY', top: '3', artist: 'Aya Nakamura', title: 'Djadja' },
{ link: 'fC6YV65JJ6g', top: '4', artist: 'Maître Gims & Vianney', title: 'La Même' },
{ link: 'hQU_pgyCL6k', top: '5', artist: 'Dadju', title: 'Bob Marley' },
{ link: 'xpVfcZ0ZcFM', top: '6', artist: 'Drake God\'s', title: 'Plan' },
{ link: 'RHb5LKnnxLg', top: '7', artist: 'Vegedream', title: 'Ramenez la coupe à la maison' },
{ link: 'BQ0mxQXmLsk', top: '8', artist: 'Camila Cabello featuring Young Thug', title: 'Havana' },
{ link: 'DkeiKbqa02g', top: '9', artist: 'Calvin Harris & Dua Lipa', title: 'One Kiss' },
{ link: '_I_D_8Z4sJE', top: '10', artist: 'Nicky Jam & J Balvin', title: 'X' },
]
},
{
year: '2017',
items: [
{ link: 'JGwWNGJdvx8', top: '1', artist: 'Ed Sheeran', title: 'Shape of You' },
{ link: 'kJQP7kiw5Fk', top: '2', artist: 'Luis Fonsi featuring Daddy Yankee', title: 'Despacito' },
{ link: 'tul6zYBp9tA', top: '3', artist: 'Niska', title: 'reseaû' },
{ link: 'GGhKPm18E48', top: '4', artist: 'Damso', title: 'Macarena' },
{ link: 'NuXaPB_KWg8', top: '5', artist: 'Lartiste featuring Awa Imani', title: 'Chocolat' },
{ link: 'UPnMFUsKm8w', top: '6', artist: 'Kalash featuring Damso', title: 'Mwaka Moon' },
{ link: 'L3wKzyIN1yk', top: '7', artist: 'Rag\'n\'Bone Man', title: 'Human' },
{ link: 'xvZqHgFz51I', top: '8', artist: 'Future', title: 'Mask Off' },
{ link: 'wuCK-oiE3rM', top: '9', artist: 'Petit Biscuit', title: 'Sunset Lover' },
{ link: 'CTFtOOh47oo', top: '10', artist: 'French Montana featuring Swae Lee', title: 'Unforgettable' },
]
},
{
year: '2016',
items: [
{ link: '2Y6Nne8RvaA', top: '1', artist: 'Kungs vs Cookin`\' on 3 Burners featuring Kylie Auldist', title: 'This Girl' },
{ link: 'b1_B-IKEufg', top: '2', artist: 'Imany', title: 'Don\'t Be So Shy (Filatov & Karas remix)' },
{ link: 'nYh-n7EOtMA', top: '3', artist: 'Sia featuring Sean Paul', title: 'Cheap Thrills' },
{ link: 'ru0K8uYEZWw', top: '4', artist: 'Justin Timberlake', title: 'Can\'t Stop the Feeling!' },
{ link: 'SxyahDOtqZw', top: '5', artist: 'Céline Dion', title: 'Encore un soir' },
{ link: 'pXRviuL6vMY', top: '6', artist: 'Twenty One Pilots', title: 'Stressed Out' },
{ link: '1LXsm9y-z3I', top: '7', artist: 'Matt Simons', title: 'Catch & Release (Deepend remix)' },
{ link: 'YxCTYUG0sow', top: '8', artist: 'Alan Walker featuring Iselin Solheim', title: 'Faded' },
{ link: 'KDXOzr0GoA4', top: '9', artist: 'Jain', title: 'Come' },
{ link: 'iAbnEUA0wpA', top: '10', artist: 'Drake featuring Wizkid & Kyla', title: 'One Dance' },
]
},
{
year: '2015',
items: [
{ link: 'jGflUbPQfW8', top: '1', artist: 'OMI', title: 'Cheerleader (Felix Jaehn remix)' },
{ link: 'OPf0YbXqDm0', top: '2', artist: 'Mark Ronson featuring Bruno Mars', title: 'Uptown Funk' },
{ link: 'YqeW9_5kURI', top: '3', artist: 'Major Lazer & DJ Snake featuring MØ', title: 'Lean On' },
{ link: 'pwKkjLOHd7s', top: '4', artist: 'Marina Kaye', title: 'Homeless' },
{ link: 'YQHsXMglC9A', top: '5', artist: 'Adele', title: 'Hello' },
{ link: 'Mptdcx36qZU', top: '6', artist: 'Feder featuring Lyse', title: 'Goodbye' },
{ link: 'PVjiKRfKpPI', top: '7', artist: 'Hozier', title: 'Take Me to Church' },
{ link: 'rs40yxHjTxQ', top: '8', artist: 'Christine and the Queens', title: 'Christine' },
{ link: 'W6cp9FakTlo', top: '9', artist: 'Louane', title: 'Avenir' },
{ link: 'hXI8RQYC36Q', top: '10', artist: 'Nicky Jam & Enrique Iglesias', title: 'El Perdón' },
]
},
];
import { Pipe, PipeTransform } from '@angular/core';
import { DomSanitizer } from '@angular/platform-browser';
@Pipe({ name: 'safe' })
export class SafePipe implements PipeTransform {
constructor(private sanitizer: DomSanitizer) { }
transform(url) {
return this.sanitizer.bypassSecurityTrustResourceUrl(url);
}
}
<div class="row pb-4">
<div class="col-12 col-sm-12 col-md-5 col-lg-5 col-xl-5">
<div class="text-center col">
<h1 class="h4">Feature : Services</h1>
</div>
<div class="card bg-light mb-3 text-center">
<div class="card-body" *ngIf="songSelected">
<h5 class="card-title text-primary">{{ songSelected.artist }}</h5>
<h6 class="card-title text-info">{{ songSelected.title }}</h6>
<h6 class="card-title text-secondary">{{ songSelected.top }}</h6>
</div>
<div class="card-body" *ngIf="!songSelected">
<div class="alert alert-danger" role="alert">
Select a song on the list to see the clip<br>
<i class="fab fa-youtube fa-2x text-danger"></i>
</div>
</div>
</div>
<div *ngIf="songSelected" class="embed-responsive embed-responsive-16by9">
<iframe class="embed-responsive-item" [src]="('https://www.youtube.com/embed/' + songSelected.link) | safe"
allowfullscreen></iframe>
</div>
</div>
<div class="col-12 col-sm-12 col-md-7 col-lg-7 col-xl-7">
<div class="form-group">
<label class="text-primary font-weight-bold" for="exampleFormControlSelect1">Classement Mégafusion
(Téléchargements + Streaming)</label>
<select class="form-control text-primary font-weight-bold" id="exampleFormControlSelect1" (change)="onChange($event)">
<option value="1">2018</option>
<option value="2">2017</option>
<option value="3">2016</option>
<option value="4">2015</option>
</select>
</div>
<table class="table table-hover table-striped table-bordered table-responsive-md table-sm">
<thead>
<tr>
<th class="text-secondary" style="text-align:center;">Clip</th>
<th class="text-secondary" style="text-align:center;">Top</th>
<th class="text-primary" style="text-align:center;">Artist</th>
<th class="text-info" style="cursor: pointer;text-align:center;">Title</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let song of songs" class="table-{{ song.class }}" (click)="select(song)">
<td class="text-secondary" style="cursor: pointer;text-align:center"><i
class="fab fa-youtube text-danger"></i></td>
<td class="text-secondary" style="cursor: pointer;text-align:center">{{ song.top }}</td>
<td class="text-primary" style="cursor: pointer;text-align:center">{{ song.artist }}</td>
<td class="text-info" style="cursor: pointer;text-align:center">{{ song.title }}</td>
</tr>
</tbody>
</table>
</div>
</div>
import { Component, OnInit } from '@angular/core';
import { Song } from './song/song';
import { SongService } from './song/song.service';
@Component({
selector: 'app-services',
templateUrl: './services.component.html',
styleUrls: ['./services.component.css']
})
export class ServicesComponent implements OnInit {
songs: Song[];
songSelected: Song;
yearSelected: number;
toto = 'JGwWNGJdvx8';
constructor(private songService: SongService) {
this.yearSelected = 1;
}
ngOnInit() {
this.getSongs(this.yearSelected);
}
getSongs(year: number): void {
this.songs = this.songService.getSongs(year);
}
select(song: Song) {
this.songSelected = song;
}
onChange($event: any) {
this.yearSelected = $event.target.value;
this.songs = this.songService.getSongs(this.yearSelected);
this.songSelected = null;
}
}
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { ServicesComponent } from './services.component';
import { SafePipe } from './safe.pipe';
describe('ServicesComponent', () => {
let component: ServicesComponent;
let fixture: ComponentFixture<ServicesComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
ServicesComponent,
SafePipe,
]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(ServicesComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { NgModule } from '@angular/core';
import { FormsModule } from '@angular/forms';
import { CommonModule } from '@angular/common';
import { ServicesComponent } from './services.component';
import { ServicesRoutingModule } from './services-routing.module';
import { SafePipe } from './safe.pipe';
@NgModule({
imports: [
CommonModule,
FormsModule,
ServicesRoutingModule
],
exports: [
ServicesComponent
],
declarations: [
ServicesComponent,
SafePipe,
],
providers: [
],
})
export class ServicesModule { }
Tests
Il ne reste plus qu'à tester les différents scripts Angular.
# Développement
npm run start
http://localhost:4200/
# Tests
npm run lint
npm run test
# Compilation
npm run build
Code source
Le code source utilisé en début de tutoriel est disponible sur github
https://github.com/ganatan/angular-react-bootstrap
Les étapes suivantes vous permettront d'obtenir une application prototype.
- Etape 5 : Modules avec Angular
- Etape 6 : Search Engine Optimization avec Angular
- Etape 7 : Progressive Web App avec Angular
- Etape 8 : Server Side Rendering avec angular
- Etape 9 : HttpClient avec Angular
- Etape 10 : Transfer State avec Angular
Les étapes suivantes vous permettront d'améliorer ce prototype
- Components avec Angular
- Services avec Angular
- Template Driven Forms avec Angular
- Charts avec Angular
Cette dernière étape permet d'obtenir un exemple d'application
Le code source de cette application finale est disponible sur GitHub
https://github.com/ganatan/angular-app