Services Overview
The Trippins frontend uses 6 core services to handle business logic, HTTP communication, authentication, and UI interactions. All services use Angular’s @Injectable decorator with providedIn: 'root' for singleton instances.
Authentication AuthService
Housing Management HousingServiceService
Reservations ReservationServiceService
Reviews ReviewServiceService
User Management UserServiceService
Authentication Service
Handles user authentication, JWT token management, and role-based authorization.
Service Implementation
src/app/services/auth.service.ts
import { Injectable } from '@angular/core' ;
import { HttpClient } from '@angular/common/http' ;
import { Router } from '@angular/router' ;
import { Observable , catchError , map , of , tap } from 'rxjs' ;
import { AuthenticationRequest , AuthenticationResponse , UserDTO } from '../models/DTOS/user-dto' ;
import { environment } from '../../environments/environment' ;
@ Injectable ({
providedIn: 'root'
})
export class AuthService {
private readonly JWT_KEY = 'auth_jwt' ;
private readonly ROLES_KEY = 'user_roles' ;
private username : string | null = null ;
constructor ( private http : HttpClient ) {}
login ( credentials : { email : string ; password : string }) : Observable < AuthenticationResponse > {
return this . http . post < AuthenticationResponse >(
` ${ environment . baseUrlApi } /login` ,
credentials
). pipe (
tap ( response => {
this . storeJwt ( response . jwt );
this . storeRoles ( response . roles );
this . username = credentials . email ;
})
);
}
private storeJwt ( jwt : string ) : void {
localStorage . setItem ( this . JWT_KEY , jwt );
}
private storeRoles ( roles : string []) : void {
localStorage . setItem ( this . ROLES_KEY , JSON . stringify ( roles ));
}
getJwt () : string | null {
return localStorage . getItem ( this . JWT_KEY );
}
getRoles () : string [] {
const rolesJson = localStorage . getItem ( this . ROLES_KEY );
return rolesJson ? JSON . parse ( rolesJson ) : [];
}
hasRole ( role : string ) : boolean {
return this . getRoles (). includes ( role );
}
hasAnyRole ( roles : string []) : boolean {
const currentRoles : string [] = this . getRoles ();
for ( let index = 0 ; index < roles . length ; index ++ ) {
if ( currentRoles . includes ( roles [ index ])) {
return true ;
}
}
return false ;
}
isLoggedIn () : boolean {
return !! this . getJwt ();
}
logout () : void {
localStorage . removeItem ( this . JWT_KEY );
localStorage . removeItem ( this . ROLES_KEY );
}
getUsername () : string | null {
if ( this . username ) return this . username ;
// Fallback to JWT parsing if username not set
const token = this . getJwt ();
if ( ! token ) return null ;
try {
const payload = JSON . parse ( atob ( token . split ( '.' )[ 1 ]));
return payload . sub || payload . username || null ;
} catch ( e ) {
return null ;
}
}
updateUsername ( newUsername : string ) : void {
this . username = newUsername ;
}
getUserDni () : Observable < string | null > {
const usernameemail = this . getUsername ();
return this . http . get < UserDTO []>( ` ${ environment . baseUrlApi } /users` ). pipe (
map ( users => {
const user = users . find ( u => u . email === usernameemail );
return user ? user . dni : null ;
}),
catchError ( err => {
console . error ( 'Error loading user data:' , err );
return of ( null );
})
);
}
getUserName () : Observable < string | null > {
const usernameemail = this . getUsername ();
return this . http . get < UserDTO []>( ` ${ environment . baseUrlApi } /users` ). pipe (
map ( users => {
const user = users . find ( u => u . email === usernameemail );
return user ? user . name : null ;
}),
catchError ( err => {
console . error ( 'Error loading user data:' , err );
return of ( null );
})
);
}
}
AuthService Methods
Purpose : Authenticate user and store JWT tokenParameters :
credentials: Object with email and password
Returns : Observable<AuthenticationResponse>Side Effects :
Stores JWT token in localStorage
Stores user roles in localStorage
Sets username in memory
Purpose : Retrieve stored JWT tokenReturns : string | nullUsage : Used by AuthInterceptor to attach token to requests
Purpose : Get user’s roles from localStorageReturns : string[] (e.g., ["ROLE_USER", "ROLE_ADMIN"])Usage : Role-based UI rendering and authorization
hasRole(role) / hasAnyRole(roles)
Purpose : Check if user has specific role(s)Parameters :
role: Single role string
roles: Array of role strings
Returns : booleanUsage : Guards, conditional rendering, authorization logic
Purpose : Check if user is authenticatedReturns : booleanUsage : Route guards, header navigation display
Purpose : Clear authentication dataSide Effects :
Removes JWT from localStorage
Removes roles from localStorage
getUserDni() / getUserName()
Purpose : Fetch additional user details from APIReturns : Observable<string | null>Usage : Profile component, reservation forms
Usage Example
Login Component
Header Component
Template Usage
import { AuthService } from '../../services/auth.service' ;
export class LoginComponent {
constructor (
private authService : AuthService ,
private router : Router
) {}
onSubmit () : void {
const { email , password } = this . loginForm . value ;
this . authService . login ({ email , password }). subscribe ({
next : () => {
this . router . navigate ([ '/' ]);
},
error : ( err ) => {
this . errorMessage = 'Invalid credentials' ;
}
});
}
}
Housing Service
Manages hotel/housing listings, search, and image uploads.
src/app/services/housing-service.service.ts
import { HttpClient } from '@angular/common/http' ;
import { Injectable } from '@angular/core' ;
import { Observable } from 'rxjs' ;
import { HousingDTO , PagedResponse } from '../models/DTOS/housing-dto' ;
import { environment } from '../../environments/environment' ;
@ Injectable ({
providedIn: 'root'
})
export class HousingServiceService {
constructor ( private http : HttpClient ) {}
getRooms ( page : number , size : number ) : Observable < PagedResponse < HousingDTO >> {
return this . http . get < PagedResponse < HousingDTO >>(
` ${ environment . baseUrlApi } /rooms/extra?page= ${ page } &size=6`
);
}
searchHouses ( tags : string , stars : number ) : Observable < any []> {
return this . http . get < any []>(
` ${ environment . baseUrlApi } /query?tags= ${ tags } &stars= ${ stars } `
);
}
getSpecificRoom ( code : number ) : Observable < HousingDTO > {
return this . http . get < HousingDTO >(
` ${ environment . baseUrlApi } /houses/ ${ code } `
);
}
createRoom ( data : any ) : Observable < HousingDTO > {
return this . http . post < HousingDTO >(
` ${ environment . baseUrlApi } /houses` ,
data
);
}
uploadHousingImage ( code : number , image : File ) : Observable < HousingDTO > {
const formData = new FormData ();
formData . append ( 'file' , image );
return this . http . put < HousingDTO >(
` ${ environment . baseUrlApi } /houses/ ${ code } /image` ,
formData
);
}
}
HousingService Methods
Method Endpoint Purpose Return Type getRooms(page, size)GET /rooms/extraPaginated hotel listings Observable<PagedResponse<HousingDTO>>searchHouses(tags, stars)GET /querySearch by tags and rating Observable<HousingDTO[]>getSpecificRoom(code)GET /houses/{code}Single hotel details Observable<HousingDTO>createRoom(data)POST /housesCreate new hotel Observable<HousingDTO>uploadHousingImage(code, image)PUT /houses/{code}/imageUpload hotel image Observable<HousingDTO>
Usage Example
export class RoomComponent implements OnInit {
houses : any [] = [];
currentPage = - 1 ;
pageSize = 6 ;
hasMore = true ;
constructor ( private houseService : HousingServiceService ) {}
ngOnInit () : void {
this . loadHouses ();
}
loadHouses () : void {
if ( ! this . hasMore ) return ;
this . currentPage ++ ;
this . houseService . getRooms ( this . currentPage , this . pageSize ). subscribe ({
next : ( response ) => {
this . houses = [ ... this . houses , ... response . content ];
this . hasMore = ! response . last ;
},
error : ( err ) => {
console . error ( 'Error loading houses:' , err );
}
});
}
onSearchSubmit () : void {
this . houseService . searchHouses ( this . tags , this . stars ). subscribe ({
next : ( houses ) => {
this . houses = houses ;
}
});
}
}
The PagedResponse interface matches Spring Boot’s pagination structure with content, last, totalPages, etc.
Reservation Service
Handles booking creation and retrieval.
src/app/services/reservation-service.service.ts
import { HttpClient } from '@angular/common/http' ;
import { Injectable } from '@angular/core' ;
import { environment } from '../../environments/environment.development' ;
import { Observable } from 'rxjs' ;
import { ReservationDTO } from '../models/DTOS/reservation-dto' ;
@ Injectable ({
providedIn: 'root'
})
export class ReservationServiceService {
constructor ( private http : HttpClient ) {}
createReservation ( data : any ) : Observable < ReservationDTO > {
return this . http . post < ReservationDTO >(
` ${ environment . baseUrlApi } /reservations` ,
data
);
}
getAllReservations () : Observable < ReservationDTO []> {
return this . http . get < ReservationDTO []>(
` ${ environment . baseUrlApi } /reservations`
);
}
}
Usage Example
export class RoomDetailsComponent {
reservationForm : FormGroup ;
constructor (
private fb : FormBuilder ,
private reservationService : ReservationServiceService ,
private authService : AuthService
) {
this . reservationForm = this . fb . group ({
checkIn: [ '' , Validators . required ],
checkOut: [ '' , Validators . required ]
});
}
onSubmitReservation () : void {
if ( this . reservationForm . invalid || ! this . house ) return ;
const reservationData = {
... this . reservationForm . value ,
housingCode: this . house . code ,
housingName: this . house . name ,
clientDni: this . clientDni ,
valorated: false
};
this . reservationService . createReservation ( reservationData ). subscribe ({
next : () => {
Swal . fire ({
title: '¡Reserva exitosa! 🎉' ,
icon: 'success'
});
this . reservationForm . reset ();
},
error : ( err ) => {
Swal . fire ({
title: 'Error creating reservation' ,
icon: 'error'
});
}
});
}
}
Review Service
Manages hotel reviews and ratings.
src/app/services/review-service.service.ts
import { HttpClient } from '@angular/common/http' ;
import { Injectable } from '@angular/core' ;
import { Observable } from 'rxjs' ;
import { PagedResponse } from '../models/DTOS/housing-dto' ;
import { ReviewDTO } from '../models/DTOS/review-dto' ;
import { environment } from '../../environments/environment.development' ;
@ Injectable ({
providedIn: 'root'
})
export class ReviewServiceService {
constructor ( private http : HttpClient ) {}
getPaginatedComments ( code : number , page : number ) : Observable < PagedResponse < ReviewDTO >> {
return this . http . get < PagedResponse < ReviewDTO >>(
` ${ environment . baseUrlApi } /rooms/ ${ code } /comments/extra?page= ${ page } &size=6`
);
}
createReview ( commentData : any ) : Observable < ReviewDTO > {
return this . http . post < ReviewDTO >(
` ${ environment . baseUrlApi } /reviews` ,
commentData
);
}
}
Usage Example
export class RoomDetailsComponent {
comments : ReviewDTO [] = [];
commentForm : FormGroup ;
constructor ( private reviewService : ReviewServiceService ) {
this . commentForm = this . fb . group ({
comment: [ '' , [ Validators . required , Validators . minLength ( 10 )]],
rating: [ 50 , [ Validators . required , Validators . min ( 0 ), Validators . max ( 100 )]]
});
}
loadComments ( code : number , page : number ) : void {
this . reviewService . getPaginatedComments ( code , page ). subscribe ({
next : ( response ) => {
this . comments = [ ... this . comments , ... response . content ];
this . currentPage = page ;
}
});
}
onSubmitComment () : void {
const commentData = {
... this . commentForm . value ,
hotelCode: this . house . code ,
userDni: this . clientDni ,
userName: this . clientName
};
this . reviewService . createReview ( commentData ). subscribe ({
next : () => {
Swal . fire ({ title: 'Review submitted!' , icon: 'success' });
this . loadComments ( this . house ! . code , 1 );
}
});
}
}
User Service
Handles user account operations.
src/app/services/user-service.service.ts
import { HttpClient } from '@angular/common/http' ;
import { Injectable } from '@angular/core' ;
import { Observable } from 'rxjs' ;
import { UserDTO } from '../models/DTOS/user-dto' ;
import { environment } from '../../environments/environment.development' ;
@ Injectable ({
providedIn: 'root'
})
export class UserServiceService {
constructor ( private http : HttpClient ) {}
createAccount ( data : any ) : Observable < UserDTO > {
return this . http . post < UserDTO >(
` ${ environment . baseUrlApi } /users` ,
data
);
}
updateAccount ( data : any , dni : string ) : Observable < UserDTO > {
return this . http . put < UserDTO >(
` ${ environment . baseUrlApi } /users/ ${ dni } ` ,
data
);
}
}
Usage Example
export class ProfileComponent {
constructor ( private userService : UserServiceService ) {}
onSubmit () : void {
if ( this . profileForm . invalid || ! this . user ) return ;
const updatedUser = {
... this . profileForm . value ,
dni: this . user . dni ,
roles: this . user . roles
};
this . userService . updateAccount ( updatedUser , this . user . dni ). subscribe ({
next : () => {
this . isEditing = false ;
this . user = { ... this . user ! , ... updatedUser };
},
error : ( err ) => {
console . error ( 'Error updating user:' , err );
}
});
}
}
Layout Service
Handles UI interactions and jQuery-based animations.
src/app/services/layout.service.ts
import { Injectable } from '@angular/core' ;
import $ from 'jquery' ;
@ Injectable ({
providedIn: 'root'
})
export class LayoutService {
initializeDropdownHover () {
const $dropdown = $ ( ".dropdown" );
const $dropdownToggle = $ ( ".dropdown-toggle" );
const $dropdownMenu = $ ( ".dropdown-menu" );
const showClass = "show" ;
$ ( window ). on ( "load resize" , () => {
if ( window . matchMedia ( "(min-width: 992px)" ). matches ) {
$dropdown . hover (
function () {
const $this = $ ( this );
$this . addClass ( showClass );
$this . find ( $dropdownToggle ). attr ( "aria-expanded" , "true" );
$this . find ( $dropdownMenu ). addClass ( showClass );
},
function () {
const $this = $ ( this );
$this . removeClass ( showClass );
$this . find ( $dropdownToggle ). attr ( "aria-expanded" , "false" );
$this . find ( $dropdownMenu ). removeClass ( showClass );
}
);
} else {
$dropdown . off ( "mouseenter mouseleave" );
}
});
}
initializeVideoModal () {
$ ( document ). ready (() => {
let $videoSrc : string ;
$ ( '.btn-play' ). on ( 'click' , function () {
$videoSrc = $ ( this ). data ( "src" );
});
$ ( '#videoModal' ). on ( 'shown.bs.modal' , function () {
$ ( "#video" ). attr ( 'src' , $videoSrc + "?autoplay=1&modestbranding=1&showinfo=0" );
});
$ ( '#videoModal' ). on ( 'hide.bs.modal' , function () {
$ ( "#video" ). attr ( 'src' , $videoSrc );
});
});
}
checkLoggedInCookie () {
if ( document . cookie . includes ( 'justLoggedIn=true' )) {
document . cookie = 'justLoggedIn=; Path=/; Expires=Thu, 01 Jan 1970 00:00:01 GMT;' ;
setTimeout (() => {
window . location . reload ();
}, 50 );
}
}
}
LayoutService bridges Angular with legacy jQuery components like Bootstrap dropdowns and video modals.
HTTP Interceptor
Automatically attaches JWT tokens to all HTTP requests.
src/app/interceptors/auth.interceptor.ts
import { Injectable } from '@angular/core' ;
import {
HttpRequest ,
HttpHandler ,
HttpEvent ,
HttpInterceptor
} from '@angular/common/http' ;
import { Observable } from 'rxjs' ;
import { AuthService } from '../services/auth.service' ;
@ Injectable ()
export class AuthInterceptor implements HttpInterceptor {
constructor ( private authService : AuthService ) {}
intercept ( request : HttpRequest < any >, next : HttpHandler ) : Observable < HttpEvent < any >> {
const token = this . authService . getJwt ();
if ( token ) {
request = request . clone ({
setHeaders: {
Authorization: `Bearer ${ token } `
}
});
}
return next . handle ( request );
}
}
How the Interceptor Works
Intercepts All Requests : Catches every HTTP request made by the app
Checks for Token : Gets JWT from AuthService
Clones Request : Creates new request with Authorization header
Adds Bearer Token : Formats as Authorization: Bearer <token>
Passes to Next Handler : Continues request pipeline
Interceptor Registration
The interceptor is registered in the app module:
import { HTTP_INTERCEPTORS , HttpClientModule } from '@angular/common/http' ;
import { AuthInterceptor } from './interceptors/auth.interceptor' ;
@ NgModule ({
imports: [ HttpClientModule ],
providers: [
{ provide: HTTP_INTERCEPTORS , useClass: AuthInterceptor , multi: true }
]
})
export class AppModule { }
The multi: true flag allows multiple interceptors to be registered. They execute in the order they’re provided.
Service Communication Patterns
Component → Service → API
Component Calls Service
this . housingService . getRooms ( 0 , 6 )
Service Makes HTTP Request
this . http . get ( ` ${ environment . baseUrlApi } /rooms/extra` )
Interceptor Adds JWT
request . clone ({ setHeaders: { Authorization: `Bearer ${ token } ` }})
Backend Validates Token
Spring Security verifies JWT signature and permissions
Response Returns to Component
Component receives Observable data and updates view
Error Handling Pattern
this . housingService . getRooms ( page , size ). subscribe ({
next : ( response ) => {
// Success handling
this . houses = response . content ;
},
error : ( err ) => {
// Error handling
console . error ( 'Error loading houses:' , err );
if ( err . status === 401 ) {
// Unauthorized - redirect to login
this . authService . logout ();
this . router . navigate ([ 'new/login' ]);
} else if ( err . status === 403 ) {
// Forbidden - show error
this . router . navigate ([ 'new/error' ]);
} else {
// Generic error
Swal . fire ({ title: 'Error' , text: err . message , icon: 'error' });
}
},
complete : () => {
// Optional cleanup
this . isLoading = false ;
}
});
Data Transfer Objects (DTOs)
Services use TypeScript interfaces for type safety:
UserDTO
HousingDTO
ReservationDTO
ReviewDTO
export interface UserDTO {
dni : string ;
name : string ;
number : number ;
email : string ;
admin : boolean ;
roles : UserRole [];
}
export interface AuthenticationResponse {
jwt : string ;
roles : string [];
}
Service Best Practices
Use providedIn: 'root' for app-wide services
Services are automatically lazy-loaded
Single instance shared across all components
Use tap() for side effects (logging, storing tokens)
Use map() for data transformation
Use catchError() for error handling
Use switchMap() for dependent requests
Always type Observable return values
Define DTOs for all API responses
Use interfaces over types
Avoid any type
Handle errors in components, not services
Use consistent error response format
Log errors for debugging
Provide user-friendly error messages
Use environment variables for base URLs
Implement retry logic for transient failures
Cancel in-flight requests on component destroy
Use proper HTTP methods (GET, POST, PUT, DELETE)
Testing Services
Unit Testing Example
import { TestBed } from '@angular/core/testing' ;
import { HttpClientTestingModule , HttpTestingController } from '@angular/common/http/testing' ;
import { AuthService } from './auth.service' ;
import { environment } from '../../environments/environment' ;
describe ( 'AuthService' , () => {
let service : AuthService ;
let httpMock : HttpTestingController ;
beforeEach (() => {
TestBed . configureTestingModule ({
imports: [ HttpClientTestingModule ],
providers: [ AuthService ]
});
service = TestBed . inject ( AuthService );
httpMock = TestBed . inject ( HttpTestingController );
});
afterEach (() => {
httpMock . verify ();
});
it ( 'should login and store JWT' , () => {
const mockResponse = {
jwt: 'mock-token' ,
roles: [ 'ROLE_USER' ]
};
service . login ({ email: '[email protected] ' , password: 'password' }). subscribe ( response => {
expect ( response . jwt ). toBe ( 'mock-token' );
expect ( service . getJwt ()). toBe ( 'mock-token' );
expect ( service . getRoles ()). toEqual ([ 'ROLE_USER' ]);
});
const req = httpMock . expectOne ( ` ${ environment . baseUrlApi } /login` );
expect ( req . request . method ). toBe ( 'POST' );
req . flush ( mockResponse );
});
it ( 'should check if user has role' , () => {
localStorage . setItem ( 'user_roles' , JSON . stringify ([ 'ROLE_ADMIN' ]));
expect ( service . hasRole ( 'ROLE_ADMIN' )). toBe ( true );
expect ( service . hasRole ( 'ROLE_USER' )). toBe ( false );
});
});
Next Steps
Component Architecture See how components use these services
Backend API Explore API endpoints consumed by services