Updated on
Updating a resource from Angular is a round trip in two halves. We GET the owner by id, copy it into a reactive form with patchValue(), let the user edit it, and then PUT the whole object back to the same address.
The form itself is the one we built in the previous part. What is new here is loading it, addressing a single resource by id in the URL, and handling a response that has no body.
The server side of this is the update action we wrote in part 6, where the POST, PUT and DELETE actions are built. The Angular side starts where Angular Reactive Form Validation and POST Requests left off.
For the complete navigation and all the basic instructions of the Angular series, check out: Introduction of the Angular series.
http launch profile, so it listens on http://localhost:5000, the address our environment file already points at.How Do We Route to the Update Component?
Prior to any update action, we need to create our component files.
So, let’s create them with the Angular CLI command that writes the component, its template and its stylesheet under src/app/owner/owner-update:
ng g component owner/owner-update --skip-tests
There is no module to register the new component in. A standalone component names what it needs in its own imports array, so the CLI writes those three files and nothing else in the application changes.
Now, to establish the route to this component, we have to modify the routes array in the owner.routes.ts file:
export const routes: Routes = [
{ path: 'list', component: OwnerList },
{ path: 'details/:id', component: OwnerDetails },
{ path: 'create', component: OwnerCreate },
{ path: 'update/:id', component: OwnerUpdate }
];
The :id segment is what lets this route address one owner. The router puts the clicked owner’s id into the URL, and the component reads it back out of the activated route.
Now we are going to change our owner-list.html and the owner-list.ts files, to enable navigation between the OwnerList and the OwnerUpdate components:
<td><button type="button" id="update" class="btn btn-success" (click)="redirectToUpdatePage(owner.id)">Update</button></td>
And the .ts file:
public redirectToUpdatePage = (id: string) => {
const updateUrl: string = `/owner/update/${id}`;
this.router.navigate([updateUrl]);
}
At this point, we have our routing defined, and we can move forward to handle the PUT request.
How Do We Pre-fill an Angular Update Form?
The update form is the create form with one difference: it opens already filled in.
The FormGroup, the three FormControl declarations, the validators and the formControlName bindings are all the ones we built in the previous part, unchanged. Rather than reprint them, we point at what actually differs.
The template changes in exactly one place. The submit binding becomes (ngSubmit)="updateOwner(ownerForm.value)" instead of calling the creation handler.
Filling the form is the component’s job, and it happens through patchValue(). Once the GET response arrives, we hand the whole owner object to the form and Angular copies each property into the control with the matching name.
patchValue() ignores keys that have no matching control, so an object carrying an id the form never shows causes no complaint. setValue() is the strict alternative, and it throws when the shapes do not match exactly.
Let’s add the wrappers code in the owner-update.html file:
<div class="container-fluid">
<form [formGroup]="ownerForm" autocomplete="off" novalidate (ngSubmit)="updateOwner(ownerForm.value)">
<div class="card card-body bg-light mb-2 mt-2">
</div>
</form>
</div>
We already know from the previous post that the formGroup is going to contain all of the controls inside its value. This value is exactly what we send as a parameter to the updateOwner action.
Everything that belongs between the card card-body div tags is markup we have already written: the three labelled controls with their formControlName attributes, the bsDatepicker input, the error rows and the Save and Cancel buttons. Take that block from the create form we built in the previous part, or copy the whole file out of the repository.
With the template in place, the rest of the work is in the component file, which is where the form gets filled and the request goes out.
How Do We Send a PUT Request From an Angular Component?
Sending the update takes three steps, once the form is valid.
First we build a plain object shaped the way the API expects. OwnerForUpdate carries a name, a date of birth and an address, and no identifier, because the identifier travels in the URL rather than in the body.
Then we build that URL from the id we loaded the owner with. PUT addresses one specific resource, so the id belongs in the path and nowhere else.
Then we call the repository and subscribe. The success branch takes no parameter at all, because our API answers a successful PUT with 204 No Content and there is no body to read. That is why the code reads next: () => rather than naming a response object, which is the one line here that surprises people coming from the create form.
Failures need no branch here: the interceptor we registered earlier sees every one of them.
The two arrows worth comparing below are steps 3 and 7: the GET response carries the whole owner, and the PUT response carries nothing at all.
Let’s start with all the imports inside the owner-update.ts file:
import { DatePipe } from '@angular/common';
import { Component, OnInit, inject, signal } from '@angular/core';
import { FormControl, FormGroup, ReactiveFormsModule, Validators } from '@angular/forms';
import { ActivatedRoute, Router } from '@angular/router';
import { BsDatepickerDirective, BsDatepickerInputDirective } from 'ngx-bootstrap/datepicker';
import { BsModalRef, BsModalService, ModalOptions } from 'ngx-bootstrap/modal';
import { Owner } from '../../_interfaces/owner.model';
import { OwnerForUpdate } from '../../_interfaces/ownerForUpdate.model';
import { OwnerRepositoryService } from '../../shared/services/owner-repository.service';
import { SuccessModal } from '../../shared/modals/success-modal/success-modal';
Then the component’s own metadata, which is where a standalone component declares what its template uses:
@Component({
imports: [ReactiveFormsModule, BsDatepickerDirective, BsDatepickerInputDirective],
providers: [DatePipe],
selector: 'app-owner-update',
styleUrl: './owner-update.css',
templateUrl: './owner-update.html',
})
The datepicker needs two directives, not one. BsDatepickerDirective opens the calendar on the date input, and BsDatepickerInputDirective is the one that writes the picked value into the dateOfBirth control. Leave the second one out and the control is never written, which on this page would look like patchValue() failing rather than like a missing import.
DatePipe is provided on the component instead of application-wide, because only this component and the create component from the previous part ever inject it.
Next, let’s add the required properties and inject the required services:
owner = signal<Owner | undefined>(undefined); ownerForm!: FormGroup; bsModalRef?: BsModalRef; private repository = inject(OwnerRepositoryService); private router = inject(Router); private activeRoute = inject(ActivatedRoute); private datePipe = inject(DatePipe); private modal = inject(BsModalService);
The owner we load lives in a signal, the shape part 10 introduced, and it starts as undefined because the component holds nothing until the response arrives.
To continue, we have to modify the ngOnInit function and add one additional function:
ngOnInit(): void {
this.ownerForm = new FormGroup({
name: new FormControl('', [Validators.required, Validators.maxLength(60)]),
dateOfBirth: new FormControl('', [Validators.required]),
address: new FormControl('', [Validators.required, Validators.maxLength(100)])
});
this.getOwnerById();
}
private getOwnerById = () => {
const ownerId: string = this.activeRoute.snapshot.params['id'];
const ownerByIdUri: string = `api/owner/${ownerId}`;
this.repository.getOwner(ownerByIdUri)
.subscribe({
next: (own: Owner) => {
const owner: Owner = { ...own,
dateOfBirth: new Date(own.dateOfBirth)
};
this.owner.set(owner);
this.ownerForm.patchValue(owner);
}
})
}
In the ngOnInit function, we instantiate the ownerForm with all the form controls and add the validation rules. Then we call the getOwnerById function to fetch the owner with the exact id from the server.
Inside that function, we pull the id out of the activated route, build the API URI from it, and send the GET request. The response goes into a local owner constant, which then feeds two things: set, which stores it in the signal for the PUT to read later, and patchValue(), which puts its values on the screen.
One thing to pay attention to is the dateOfBirth value. The API sends a date as a string and the datepicker input expects a Date, so we build one with new Date(own.dateOfBirth). The string is ISO 8601, which the Date constructor reads the same way whatever the locale.
The validateControl and hasError helpers that drive the error messages are the ones from the validation section of the previous part, unchanged, so we can copy both of them straight across.
Now, before we send the PUT request, we are going to add one more interface:
export interface OwnerForUpdate {
name: string;
dateOfBirth: string;
address: string;
}
It has the same three fields as OwnerForCreation, and it is a separate interface on purpose. The two happen to agree today, but they answer to different actions on the server, and either one can gain a field without dragging the other along.
And also modify the updateOwner function inside the owner-repository.service file, so it takes the new type:
public updateOwner(route: string, owner: OwnerForUpdate) {
return this.http.put(this.createCompleteRoute(route, this.envUrl.urlAddress), owner);
}
Finally, we are going to return to our component file, and execute the update action:
public updateOwner = (ownerFormValue: any) => {
if (this.ownerForm.valid)
this.executeOwnerUpdate(ownerFormValue);
}
private executeOwnerUpdate = (ownerFormValue: any) => {
const ownerForUpd: OwnerForUpdate = {
name: ownerFormValue.name,
dateOfBirth: this.datePipe.transform(ownerFormValue.dateOfBirth, 'yyyy-MM-dd') ?? '',
address: ownerFormValue.address
}
const apiUri: string = `api/owner/${this.owner()?.id}`;
this.repository.updateOwner(apiUri, ownerForUpd)
.subscribe({
next: () => {
const config: ModalOptions = {
initialState: {
modalHeaderText: 'Success Message',
modalBodyText: 'Owner updated successfully',
okButtonText: 'OK'
}
};
this.bsModalRef = this.modal.show(SuccessModal, config);
this.bsModalRef.content?.redirectOnOk.subscribe(() => this.redirectToOwnerList());
}
})
}
This is pretty much the same logic as the createOwner function, with two differences. The URL carries the id, read back out of the signal with this.owner()?.id, and the next callback declares no parameter, because the server sends a 204 and there is nothing to read.
The outgoing date goes the other way through DatePipe: the form holds a Date, and transform(..., 'yyyy-MM-dd') turns it into the string the API’s model binder accepts.
Nothing in this component reacts to a failed request. The error interceptor from Angular Error Handling: HTTP Errors and Error Pages catches every one of them, sends a 404 or a 500 to its error page, and shows the error modal for anything else.
Of course, we also need that redirectToOwnerList function:
public redirectToOwnerList = () => {
this.router.navigate(['/owner/list']);
}
Now we can try it. With the API running, open the owner list, click Update on a row, change the name and save. The request comes back 204 No Content, the modal reads “Owner updated successfully”, and clicking OK sends us back to the list, which fetches the owners again and shows the new value.
When Should We Use PUT Instead of PATCH?
PUT replaces a resource. PATCH changes part of one.
A PUT body is the complete new state of the resource at that address. Whatever the body leaves out, the server is entitled to treat as absent, so sending only a changed name can blank the other fields. Our form submits all three properties on every save, which is why PUT is the honest verb here.
A PATCH body describes a change instead: which fields to set, and to what. It suits a screen that edits one field on its own, and it costs less over the wire on a large resource.
PUT is idempotent. Sending the same request twice leaves the resource in the state one request would have left it in, so a client retrying after a timeout cannot do harm. POST is not idempotent, which is why creating twice makes two owners.
| Method | What the request body means | Idempotent | Typical success response | Reach for it when |
|---|---|---|---|---|
POST | Data for the server to process however it chooses | No | 201 Created with a Location header | The server decides the new resource's URL |
PUT | The complete new state of the resource at this URL | Yes | 204 No Content, or 200 OK with the resource | The client already knows the resource's URL |
PATCH | A description of the changes to apply | Not required to be | 204 No Content, or 200 OK with the resource | Only part of the resource is changing |
Our API has no PATCH action, so there is nothing to call here yet. If you want to add one, how PATCH requests are built covers the document format and the server side of it.
A .NET application sends the same requests, and sending the same requests from a .NET client shows what changes when the caller is HttpClient rather than a browser.
Conclusion
In this article, we have learned how to pre-fill a reactive form with patchValue(), how to address a single owner by putting the id in the URL, and how to send a PUT request whose response carries no body at all. We have also seen when PUT is the right verb and when a PATCH would fit the screen better.
In the next part of the series, Angular DELETE Request to an ASP.NET Core Web API, we are going to write the delete part of the project, and slowly wrap the coding part of the series up.
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.


Hello Marinko. Thank you very much for these amazing tutorials. I have a question. When trying to update the Date of Birth I am getting an error on the Error module windows saying “Error Message: [object Object] I ready don’t understand what the problem is, since I downloaded your code and tested thinking that probably I was doing something wrong, but your code displays the same error. This only happens when trying to update the DateOfBird, if I update name or address I get Successful message.
Also when creating a new Onwer it works perfect, I can select the Date Of Birth just fine, when I save, it records the row on the database just fine.
Please any help would be really appreciated.
Thank you very much in advance.
Hi I have a question. Does your angular solution handle DataAnnotations ( [Required(ErrorMessage = “Name is required”)]) which you have marked in Owner.cs in your .net backend?
or are you basically repeating the validation in the angular component?
Hello Truth. Well you are correct and you are not 😀 This is the Angular series, and we show you how to consume the .NET Core Web API server application. So basically when you write your client app, you can’t rely on the server side validation, because you don’t know is it written at all. Same way goes for the server side.
In this situation we are writing both sides (server and client) but in many cases you would maybe just write consumer app or just the server app, so in that way you have to secure your app side. This is why I have created validations on both sides.
I understand why did you ask this question, but I hope my answer clarifies that.
Yes you answered it thanks. Web api with mvc razor pages and fluent validation, one is able to get away with just validating in backend api and it some still generates client side code. That way you dont have validation written twice. Im new to angular so seeing if there is something similar.
Good morning Marinko. Im writing to you because i have a problem at this point and i dont know how to solve it. Could be fantastic if u can help me. I have a problem when I press the button from the list to update one of the “owners” (i named it TiposProyecto).
When i press the button in the owner-list to update, it jumps to the /404 route.
I have no errors in the code but when I press F12 in google chrome to see the developers console, I get this error. Also i have the same problem when i want to delete from the list
zone.js:2969 GET http://localhost:5000/api/TiposProyecto/undefined 404 (Not Found)
Could you tell me how to solve this problem?
Thx dude
Hello Sergio. Thank you very much for reading our posts. I hope it is helpful to you. Let me try to help you about your error.
First of all i f you look at your GET request (GET http://localhost:5000/api/TiposProyecto/undefined) you are going to see the undefined part. This is the place where the GUID of your owner object (that you want to update) should be placed. But you are mapping it wrong thus having the undefined value. Your app works perfectly because your server can’t find the user with the none existing Id and returns to you 404 NotFound().
Now how to solve it:
1) Take a look at your owner.module.ts file, there must be these two lines of code
{ path: ‘update/:id’, component: OwnerUpdateComponent },
{ path: ‘delete/:id’, component: OwnerDeleteComponent }
2) Take a look at your owner-list..component.html file. Both update and delete buttons should call the corresponding functions and pass the owner.id parameter. Also check if your owner object returned from the server has that id property.
If none of this helps, you could update your project and send me the link. Then I could take a look at it. It seems as if you made mistake in the owner.module file or that you don’t have the id property (maybe it is named ownerId or something like that)
At this point this is all I could do, until you send me your code.
All the best mate.
Good morning, Marinko. First of all, I wanted to thank you for helping me out the other day. As you said, I was mapping the id incorrectly, specifically in the list-component.html form. Now when I press the update or delete button from the list, I can access into both forms for that particular id that I’m accessing without problems.
But I have another problem and I’d like to tell you if you can help me. As I told you before, I can now access both the update and delete forms of a specific id, but when I click the button to confirm the changes, the modal window doesnt appear and the google chrome development console gives me these errors:
PUT http://localhost:5000/api/TiposProyecto/undefined 400 (Bad Request) :5000/api/TiposProyecto/undefined:1
DELETE http://localhost:5000/api/TiposProyecto/undefined 404 (Not Found)
5000/api/TiposProyecto/undefined:1
If you can help me out here, I’d really appreciate it, thanks again, Marinko.
Hello Sergio. I am sorry to see that you still have some problems, but I am glad that I have helped you previously. So I will try to do it again. It is very strange that you are not getting any modal messages, I have tried to simulate your bad requests and the modal window is always there with appropriate message. So, probably you have some wrong implementation in there as well.
Concerning your current problem, as you may see, you have undefined part again in your requests. So I am assuming that your object id is undefined and maybe complete object is bad. Do you see any data populated in the input fields, once you land on the update page? If not than something is wrong with your object retrieved from the server while landing the Update or Delete page.
Again, I can’t help you a lot like this. All I know is that your Id param i undefined. It could help you if you could place a break point inside chrome developer tools to the place where you send that request and to inspect what is the Id value.
Finally you can upload your code and send me the link, than I will go through it and try to find what is wrong.
All the best mate.
Hi Marko,
Thanks for putting this tutorial, its awesome..
Sergio,
I was also facing the same problem. I think the solution is a change in the owner.model.ts In one of the previous tutorials, it was changed to “ownerId”, change it to “id”. It should work.
OwnerUpdateComponent.ts makes us of this in the “let apiUrl = `api/owner/${this.owner.id}`;” and intellisence suggests this.owner.ownerId and then the trickle effect is Api is having an issue.
Hope this 2 cents helps.
Thanks,
Hello Asterix. First of all thank you for the kind words. Second thing, thank you so much on your suggestion. When I have red your comment I thought whaaat??? why would I leave the ownerId property anywhere? And then went through all of the articles and in part 11 Error Handling I saw it 😀 😀 My god. I have changed the owner.module.ts file by adding an Account array and the God knows why, I left ownerId instead of id. Can’t believe I did that 😀 One more time, thank you so much.
All the best mate.
Thanks Marinko,
My apologies for calling you Marko.
I am looking forward to more of your tutorials. Can you build up on this and make a series on OAuth for Core webapi?
Thanks
You don’t have to apologizes it is almost the same 😀 We have published JWT with .NET Core and Angular, maybe you will find that interesting: http://34.65.74.140/authentication-aspnetcore-jwt-1/ Also we have the web api best practices article, so you may find a lot of good advice there. About OAuth, it is a good idea, right now we have some plans but as soon as we find spare time, it would be done.
One more time thank you a lot, it is always a joy to talk to you.
hey man, when i click in update, miss something datePipe and a looked in google to solve the trouble and this way is possible
solve :
in class owner-update-component
put
@Component({
providers: [DatePipe]
});
Hi Leonardo. I believe your solution is just fine but there is no need to it. Again I have checked my code and it is working perfectly. You don’t have to populate providers array inside the owner-update-component, you may do exactly as I wrote in the article:
“But to make it work inside a component, we need to import the DatePipe pipe and inject it inside the constructor. We also need to import the DatePipe inside the app.module.ts file and to place it inside the “providers” array.”
So as you may see, you need to import the DataPipe pipe in a component, to register it in a constructor and as well to import it in the app.module and to place it in the providers array.
With your way you have registered your pipe just in one component and to use it in another you need again to put it in providers array of the component. But with my way, you only need to import it and register it with the constructor method.
All the best.