Updated on
While sending HTTP requests to our server, we need to use the Angular HttpClient. Of course, we may handle all the HTTP requests from every component and process the response as well, but it is not a good practice. It is much better to make one repository for our requests and then send the request URI to that repository. The repository should take care of the rest.
For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.
This part continues from Angular Routing and Navigation Menu, where the application got its menu and its routes.
http launch profile, so it listens on http://localhost:5000.So, let’s start with the environment files first.
How Do Angular Environment Files Work?
An Angular environment file is a plain TypeScript module of configuration values, and the build swaps one file for another depending on which configuration we build.
The swap is not magic, and nothing reads these files at runtime. angular.json lists a fileReplacements entry for a build configuration, the compiler substitutes one file path for the other, and the application only ever imports the one path.
The CLI does not create the folder for us. ng generate environments writes src/environments/environment.ts alongside environment.development.ts and adds the replacement entry to angular.json.
The direction catches people out. environment.ts is the base file, and it is what a production build uses. The development configuration is the one that replaces it, with environment.development.ts.
Everything the application imports comes from the base path. Code always writes import { environment } from '../environments/environment', never the development file, because the whole point is that the import does not change.
Our client calls http://localhost:5000 while we develop it and a public address once it is deployed, so let’s generate the pair of files that holds those two values:
ng generate environments
The CLI creates the folder, both files and the replacement entry:
CREATE src/environments/environment.ts (31 bytes) CREATE src/environments/environment.development.ts (31 bytes) UPDATE angular.json (2243 bytes)
Let’s start with environment.ts, the base file that a production build uses:
export const environment = {
urlAddress: 'http://www.accountowner.com'
};
Then let’s modify the environment.development.ts file, which the build substitutes for the base one while we work locally:
export const environment = {
urlAddress: 'http://localhost:5000'
};
Now we are going to create a service, which we can use to get the valid environment urlAddress.
Let’s create a new environment-url.service.ts file:
ng g service shared/services/environment-url --type=service
This command creates the shared/services folders and the service file inside them:
CREATE src/app/shared/services/environment-url.service.ts
The --type=service flag is what puts service in the file name and Service at the end of the class name. Without it, the CLI writes environment-url.ts holding a class called EnvironmentUrl, and every later part of this series imports EnvironmentUrlService.
Let’s continue our work with the environment files by modifying the environment-url.service.ts file:
import { Service } from '@angular/core';
import { environment } from '../../../environments/environment';
@Service()
export class EnvironmentUrlService {
urlAddress: string = environment.urlAddress;
}
The urlAddress property takes the value from whichever environment file the build used, and that choice is made in angular.json:
"configurations": {
"production": {
"budgets": [...],
"outputHashing": "all"
},
"development": {
"optimization": false,
"extractLicenses": false,
"sourceMap": true,
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.development.ts"
}
]
}
},
"defaultConfiguration": "production"
Notice which configuration carries the replacement. The production configuration has no fileReplacements at all, and the build target’s defaultConfiguration is production, so a bare ng build reads environment.ts untouched. It is ng serve, which builds with the development configuration, that swaps in the other file. Angular’s Build environments guide says the same thing about that base file: “The project’s src/environments/ directory contains the base configuration file, environment.ts, which provides the default configuration for production.”
If we have worked with the older model, where environment.prod.ts replaced environment.ts for production, nothing about our two commands changes: ng serve still talks to localhost and ng build still ships the public address. Only the direction of the replacement is inverted.
A production build is where that swap becomes visible, and our guide on publishing an Angular application with ASP.NET Core takes it from there.
What Is an Angular Service?
An Angular service is an ordinary class that holds logic a component should not hold itself, and Angular’s injector creates it and hands it to whoever asks.
Registration is one decorator. ng generate service writes @Service(), which provides the class in the root injector by itself. One shared instance then serves the whole application, created the first time something asks for it.
Asking is inject(). A component writes private repo = inject(OwnerRepositoryService) and gets that instance, never calling new. Older code uses @Injectable({ providedIn: 'root' }) with a constructor parameter, and both still work.
Sharing is the point. A service holding fetched data, a cache, or an HTTP wrapper should be shared, because two components asking for it get the same object and therefore the same state.
The test is simple. If a second component would need the same code, or if the component’s class is doing work that has nothing to do with what it renders, it belongs in a service.
How Do We Wrap Angular HttpClient in a Service?
Components should not call HttpClient directly. Wrapping the calls in a service gives every component one place to ask for data and one place to change when the API does.
HttpClient is injected into the service, and the service is injected into the components. Registration is one provider: provideHttpClient() in app.config.ts.
The wrapper’s job is to turn a short route into a full URL and return the observable unchanged. getOwners('api/owner') combines the route with the base address from the environment file and calls http.get, so no component ever writes a hostname.
The generic type argument is the second reason to wrap. http.get<Owner[]>(url) tells TypeScript what the response holds, so every component consuming the service gets a typed result instead of a bare object.
Nothing is sent yet. These methods build observables and return them, and no request leaves the browser until something subscribes.
One request passes through three of our own classes before it reaches the network.
First, we register HttpClient for the whole application in the app.config.ts file:
import { ApplicationConfig, provideBrowserGlobalErrorListeners } from '@angular/core';
import { provideHttpClient } from '@angular/common/http';
import { provideRouter } from '@angular/router';
import { routes } from './app.routes';
export const appConfig: ApplicationConfig = {
providers: [
provideBrowserGlobalErrorListeners(),
provideRouter(routes),
provideHttpClient()
]
};
Older applications import HttpClientModule into an NgModule instead. That module is deprecated, and provideHttpClient() is what replaces it, so there is no imports array to touch here.
After that, let’s create a service file and name it owner-repository.service.ts. We are going to place it in the same folder in which the environment service resides. In this service, we are going to create GET, POST, PUT, and DELETE requests for the owner entity from our server API:
ng g service shared/services/owner-repository --type=service
After the file creation, we are going to create a new _interfaces folder under the app folder, and add a new owner.model.ts file:
export interface Owner {
id: string;
name: string;
dateOfBirth: Date;
address: string;
}
One thing about that interface is worth knowing before we use it. The http.get<Owner[]>() call constructs nothing: it parses the JSON response and asserts the type we gave it. JSON has no date type, so dateOfBirth arrives as a string while TypeScript believes it is a Date, and the first call to owner.dateOfBirth.getFullYear() fails at runtime with an error the compiler approved. There are two honest fixes: type the field as a string and convert it where we display it, or map the response and construct real Date objects.
Once we have our interface in place, we can modify the repository file:
import { HttpClient } from '@angular/common/http';
import { Service, inject } from '@angular/core';
import { Owner } from '../../_interfaces/owner.model';
import { EnvironmentUrlService } from './environment-url.service';
@Service()
export class OwnerRepositoryService {
private http = inject(HttpClient);
private envUrl = inject(EnvironmentUrlService);
public getOwners(route: string) {
return this.http.get<Owner[]>(this.createCompleteRoute(route, this.envUrl.urlAddress));
}
public createOwner(route: string, owner: Owner) {
return this.http.post<Owner>(this.createCompleteRoute(route, this.envUrl.urlAddress), owner);
}
public updateOwner(route: string, owner: Owner) {
return this.http.put(this.createCompleteRoute(route, this.envUrl.urlAddress), owner);
}
public deleteOwner(route: string) {
return this.http.delete(this.createCompleteRoute(route, this.envUrl.urlAddress));
}
private createCompleteRoute(route: string, envAddress: string) {
return `${envAddress}/${route}`;
}
}
Repository Code Explanation
Let’s explain this code.
First, we ask for the Angular HttpClient and our EnvironmentUrlService with the inject() function. Then we write the functions that wrap our requests. The getOwners function is a wrapper for the GET request. It accepts the route parameter of the string type (api/owner) and combines it with the address from the environment file, so in a development build the request goes to http://localhost:5000/api/owner, which is exactly where our Web API listens.
The second function, createOwner, is a wrapper for a POST request. It builds the same complete route and additionally receives a body, the entity we are creating. We set no header here, because HttpClient sends Content-Type: application/json by itself whenever the body is an object. If we ever need another header, we pass an HttpHeaders object in the options argument.
The updateOwner function is pretty much the same as the createOwner function, except that it sends the PUT request. Lastly, the deleteOwner function is a wrapper for the DELETE request, which accepts a route like api/owner/id. Both of these functions are missing a strongly typed HTTP method because we don’t expect any object as a response from the server. If everything goes well, we are going to get a 204 as a response without a response body.
Our repository wraps four of the methods HttpClient exposes, and each one of them returns an observable rather than a result:
| Method | Typical call | Returns | Sends a body |
|---|---|---|---|
| GET | http.get<T>(url) | Observable<T> | no |
| POST | http.post<T>(url, body) | Observable<T> | yes |
| PUT | http.put<T>(url, body) | Observable<T> | yes |
| PATCH | http.patch<T>(url, body) | Observable<T> | yes |
| DELETE | http.delete<T>(url) | Observable<T> | not by default |
| HEAD | http.head<T>(url) | Observable<T> | no |
| any | http.request<T>(method, url, options) | Observable<T> | depends on options |
Adding observe: 'response' to the options changes the return type to Observable<HttpResponse<T>>, which is how we read status codes and headers instead of just the body.
The Subscription on the HTTP Calls
These wrapper functions need a subscription in order to work. In this post, we are only creating a repository with the HTTP calls. But as soon as we start creating our pages, we are going to use the subscription.
For now, we are just going to show you one example of a subscription:
owners = signal<Owner[]>([]);
private repo = inject(OwnerRepositoryService);
private consumeGetFromRepository = () => {
this.repo.getOwners('api/owner')
.subscribe(own => {
this.owners.set(own);
})
}
As you may notice, we are calling the getOwners function from the repository, but that function won’t be executed until we call the subscribe function. Angular’s HttpClient guide puts the same order of events in one sentence: “Each method returns an RxJS Observable which, when subscribed, sends the request and then emits the results when the server responds.” The result from the response is going to arrive in the own parameter, and we hold it in a signal so the template re-renders as soon as it lands. Both signal and inject come from @angular/core, and the next part explains that signal beside the list it renders.
There is nothing to unsubscribe from here. An HttpClient observable emits one response and completes, while a subscription to a long-lived observable is the one that needs cleaning up.
This example handles the success path and nothing else. Handling the failure path is part 11’s subject.
Current Angular can also hand us an HTTP response as a signal instead of a subscription, with toSignal from @angular/core/rxjs-interop or with the httpResource function. We stay with subscribe because the parts that follow build on it.
Conclusion
Excellent, now we have our repository prepared and we are ready to create components, which are going to use these repository functions to show results in a browser.
In the next part, Angular Lazy Loading: Load Routes on Demand, we load a feature area on demand and show the fetched data on the page.
Tested with Angular 22.1.5, Angular CLI 22.1.7, ngx-bootstrap 22.0.0, Bootstrap 5.3.8, TypeScript 6.0.3 and Node 24.


Marinko, I have this error in repositiry service? In app.module is located import HttpClientModule. Can you help?
Hello Darko. As much as I can see in your file, you are missing these two functions:
import { Injectable } from '@angular/core'; import { HttpClient, HttpHeaders } from '@angular/common/http'; import { EnvironmentUrlService } from './environment-url.service'; @Injectable({ providedIn: 'root' }) export class RepositoryService { constructor(private http: HttpClient, private envUrl: EnvironmentUrlService) { } ... private createCompleteRoute = (route: string, envAddress: string) => { return${envAddress}/${route}; } private generateHeaders = () => { return { headers: new HttpHeaders({'Content-Type': 'application/json'}) } } }As you can inspect in our code snippet and our source code, those functions are inside the repository service file.
Thanks for replay, I inserted these two functions and it’s ok. But I have one more error on second parameter “body” in repository functions?
Try adding the any as a type. You can read here: https://stackoverflow.com/questions/43064221/typescript-ts7006-parameter-xxx-implicitly-has-an-any-type from the accepted answer.
how to work with pagination and get header information in angular api is use same as in your book but paging information is in header provide me complete code for get and post
If you have the second edition of our book, you can find an explanation of how to share your custom header with your client app. If you don’t have it you need to share it with the CORS setup by using the WithExposedHeaders method.
I have your book but i am working in angular how to make a generic repository in angular and how to get header information in angular
You can extract the custom header by using the get function and specifying the header name.
This one is just a simple example from one of my old projects:
const result = await this._repo.getProducts(‘api/products’, this._queryParams);
this.pagination = JSON.parse(result.headers.get(‘X-Pagination’));
this.products = result.body;
actually i have this code already but i want a generic repository for this type of response a batter version like you make in book for core i need same repository and i unit of work for angular project to avoid repetition of code if you have please attach code and also make book on angular project
I am sorry, but we don’t have an example like that. To be honest, in client apps I wouldn’t try to replicate the things I do on the server-side. Just keep it nice and simple, even if you use a separate repo file for each entity.
Hey Man!
Thanks for These Great full Tutorials.
On the Repository Service in Every CRUD function you pass this.envUrl.urlAddress as an Parameter
(While its remain same for all CRUD Methods)
Why Not You Send Only route Parameter on Calling from CRUD function
& On createCompleteRoute Function set the default envAddress Value to this.envUrl.urlAddress
——————————————————————–
BEFORE
—————-
public getData = (route: string) => {return this.http.get(this.createCompleteRoute(route, this.envUrl.urlAddress));
}
private createCompleteRoute = (route: string, envAddress: string) => {
return `${envAddress}/${route}`;
}
——————————————————————–
AFTER
—————-
public getData = (route: string) => {return this.http.get(this.createCompleteRoute(route));
}
private createCompleteRoute = (route: string, envAddress: string = this.envUrl.urlAddress) => {
return `${envAddress}/${route}`;
}
——————————————————————–
Is There Specific Reason or Something like Best Practice for Above Article Method of Doing This.
Again Thanks for Great Content You Create for Us. ❤
Hello. First of all, you are very welcome and thank you too for all the kind words. There is no any special reason for that, it was just the way I wrote it in that moment. Your solution is great as well and you can use it as-is. I am glad you find our tutorials valuable for you, this is the main purpose for them. Have a great day and Best Regards.
Thanks Man. ❤
Hello Sir,
This article very use full me me and everyone but i have one request to make video on it so it’s too much easy to understand. I hope you post video as soon as possible.
Thank you so much
public result: any;
constructor(private repo: RepositoryService) { }
public consumeGetFromRepository() {
this.repo.getData(‘api/owner/24fd81f8-d58a-4bcc-9f35-dc6cd5641906’)
.subscribe(res => {
this.result = res;
},
(error) => {
this.handleErrors(error);
})
}
where we put this code
Hi Dhruval. You don’t have to place it anywhere. This is just an example how the subscription works. Please read the next artilce from the same series, the link is in the Conclusion section, and you will see how to use this repository in your project.
I’ve come back to this guide again – it’s awesome!
I’m creating a single website with multiple projects: .net core Web.API and a separate Angular 6 Web project. However, they will be hosted on the same server as a single application. Do you have some suggestions/or would you change anything in this guide to achieve that?
Thanks for all your effort Marinko. Much appreciated.
Hello Matt. If you take a look at our post where we publish our .NET Core + Angular app, you will see that we publish only one application as well. In a development phase, you are using two projects (.NET Core and Angular) because it is better and cleaner approach (at least that is my opinion) but when it comes to the deployment, you will build all your client files as a production ones and then transfer them to the wwwroot folder in your .NET Core app. Only then you can publish your app.
For more detailed information, you can read: http://34.65.74.140/net-core-web-development-part16/ or http://34.65.74.140/net-core-web-development-part17/
So, I wouldn’t change anything about approach from this tutorial.
I am glad, you find these tutorials useful. Spread the word 😀 😀 And I hope I have helped you with my answer.
All the best.
Thank you so much for all the work you’ve put into this. You’ve explained a lot of little things I was foggy about. Anyway it’s been extremely useful to go through these 9 steps. I’m looking forward to the rest!
Hi Bob. You are very welcome and thank you for reading these parts and for leaving such a great comment for me. It is always a pleasure to read something like that. I hope the rest of the series is going to be beneficial to you and to all the readers. One more time, thank you very much, mate.