Introduction
Angular Services are very powerful tools that allow us to encapsulate common logic, make asynchronous requests to backends, share data between components, and more. A common thing we want services to do is make HTTP requests to backends but allow components that call the service to pass in parameters to customize the requests. This article will show how to write Angular services that accept inputs/parameters from components in order to make dynamic requests.
Why Use Inputs for Requests?
There are a few key reasons we want our services to accept inputs rather than having hardcoded requests:
Reusability – By allowing inputs, the same service can be reused across components by passing in different parameters each time. This prevents duplicating logic.
Customization – Components have full control over what data is fetched by customizing the request parameters on each call.
Abstraction – Components don’t need to know the structure of the backend requests. They just call methods on the service and pass simple parameters.
Testability – Services become highly testable since different parameters can be tested in isolation without relying on backend state.
Creating the Service
Let’s create a simple UserService that will fetch user data from a backend API. We’ll start by generating the service:
Copy
ng generate service user
This creates the user.service.ts file. Now we’ll add a method called getUser() that accepts an id parameter:
typescript
Copy
@Injectable({
providedIn: ‘root’
})
export class UserService {
constructor(private http: HttpClient) {}
getUser(id: string) {
// request code goes here
}
}
The id parameter will be used in the request to fetch a specific user by their ID.
Making the Request
Inside the getUser() method, we’ll return an Observable by calling HttpClient:
typescript
Copy
getUser(id: string): Observable
const url = `/api/users/${id}`;
return this.http.get
}
A few things to note:
We construct the URL dynamically using template strings and the id input.
The generic type
No need to subscribe – that’s left to the calling component.
This allows us to fetch any user by passing their ID into the service method.
Handling Errors
We should also handle errors properly in the service:
typescript
Copy
getUser(id: string): Observable
const url = `/api/users/${id}`;
return this.http.get
.pipe(
catchError(this.handleError)
);
}
private handleError(error: HttpErrorResponse) {
// error handling logic
return throwError(() => error);
}
By piping the result through catchError(), any errors will be passed to our custom handler instead of erroring out the subscription.
This keeps the calling component clean and prevents unhandled errors.
Using the Service
Now in our components, we can call the service method and pass parameters:
typescript
Copy
@Component({…})
export class UserComponent {
user: User;
constructor(private userService: UserService) {}
ngOnInit() {
const id = ‘abc123’;
this.userService.getUser(id)
.subscribe(
user => this.user = user
);
}
}
By passing the id, this component is dynamically fetching the user data from the backend via the service.
Adding More Methods
We can expand the service to support other requests:
typescript
Copy
@Injectable()
export class UserService {
// get user
getUser(id) {…}
// get all users
getAllUsers() {
return this.http.get
}
// create user
createUser(user) {
return this.http.post(‘/api/users’, user);
}
// delete user
deleteUser(id) {
return this.http.delete(`/api/users/${id}`);
}
}
Each method allows customization via inputs, following the same model – make request, return Observable, handle errors.
Then components can call these different methods:
typescript
Copy
// get all
this.userService.getAllUsers()
.subscribe(users => {…});
// create
const newUser = {…};
this.userService.createUser(newUser)
.subscribe({…});
This keeps components simple and testable while encapsulating all backend logic and requests in the service.
Additional Tips
Here are some other tips when creating services that accept inputs:
Consider default inputs in case none are provided
Add validate methods to validate required inputs
Allow optional/null inputs rather than throwing errors
Support request configuration options like headers
Add loaders/spinners while requests are pending
Cache responses client-side with inputs as keys
Add helper methods for common input combinations
Extract duplicate code to shared utility methods
By following these guidelines, Angular services become very reusable, customizable and testable components for handling data access and backend requests. Components remain focused on presentation while services handle all the complex logic and orchestration.
Conclusion
In this article, we explored how to write Angular services that accept input parameters in order to dynamically generate HTTP requests based on those inputs. This makes the services highly reusable across different components by allowing customization of each request. It also keeps components clean and focused on presentation by encapsulating all backend logic in the service. Overall, using input parameters is an essential technique for building robust, scalable and maintainable Angular applications.
