Angular patch observable data to reactive form fields - javascript

I've got a reactive form that I'm initializing oninit() like this, along with a couple other properties I'm using to grab the passed id out of the URL and tell whether or not the form is being used to update, or create a new entry in a mysql table. The issue I'm having is in using patchValue to pass the data returned from my service into my form:
component.ts
export class formComponent implements OnInit, AfterViewInit {
constructor(
private dataService: dataService,
private route: ActivatedRoute,
private router: Router,
private formBuilder: FormBuilder,
private ticketModel: ticketModel,
) {}
Form!: FormGroup;
isNewMode!: boolean;
id!: string;
ticket!: ticketModel[];
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
this.Form = this.formBuilder.group({
field1: ['', Validators.required],
field2: ['', Validators.required],
field3: ['', Validators.required],
field4: ['', Validators.required]
});
}
ngAfterViewInit(){
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData;
},
});
this.Form.patchValue({
field1: this.ticket.field1, //error, "Property 'field1' does not exist on type 'ticketModel[]'"
field2: this.ticket.field2, //error, "Property 'field2' does not exist on type 'ticketModel[]'"
field3: this.ticket.field3, //error, "Property 'field3' does not exist on type 'ticketModel[]'"
field4: this.ticket.field4, //error, "Property 'field4' does not exist on type 'ticketModel[]'"
});
}
}
}
ticketModel.ts
export class ticketModel {
id: string = '';
field1: string = '';
field2: string = '';
field3: string = '';
field4: string = '';
}
service.ts
export class dataService {
constructor(private errorHandlerService: errorHandlerService, private http: HttpClient) {}
private url = "/api/tickets";
httpOptions:{ headers: HttpHeaders } = {
headers: new HttpHeaders({ "Content-Type": "application/json" }),
};
getById(id: string): Observable<ticketModel[]> {
return this.http
.get<ticketModel[]>(`${this.url}/${id}`, {responseType: "json"})
.pipe(tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel[]>("fetchAll", [])
)
);
}
and just in case it's helpful, this is an example of the response json I'm getting when this method is run
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
if there isn't an id that gets passed in, isNewMode is true and the form is initialized with blank values, works fine from there. When an id is passed in, I'm passing that to a method in the data service to query the database and return just that row. This also seems to work fine as I'm able to log that data in json format to the console. I just can't figure out how to get the data to patch into the form after trying this out a few different ways.
Currently, the way that I think this should work which is what this code is an example of, in patchValue() the compiler throws an error that "property field1 does not exist on type ticketModel[]" when it absolutely does exist as a property on that model.
I feel like I'm probably missing something pretty small and any help in figuring out what would be wildly appreciated, thank you!

You have declared ticket!: ticketModel[] as an Array type.
Your response is also an array -
[{"id":18,"field1":"string data","field2":"data is here","field3":"another string goes here","field4":"this is another example string"}]
then why are you not treating this.ticket as an array here ?
field1: this.ticket.field1,
Either use it this way - field1: this.ticket[0].field1 or use for loop on it to get the field1 and other values from this.
And you need to patch the form inside the subscribe block, because it's an async operation.

really your service getById should return one 'TicketModel' nor an array of TicketModel. better than mannage in the component, mannage in the service using map
//see that is an observable of "ticketModel"
getById(id: string): Observable<ticketModel> {
//you needn't use {responseType:'json'}, by defect Angular expect a Json
return this.http
.get<ticketModel[]>(`${this.url}/${id}`)
.pipe(
//just return the uniq element of the array
map(res=>res.length?res[0]:null),
tap((_) => console.log('returned by service: ', JSON.stringify(_))),
catchError(
this.errorHandlerService.handleError<ticketModel>("fetchAll", [])
)
);
}
Futhermore, you need use the "patchValue" inside subcription function, and you can use the patchValue wihout create a new object because has the same properties
if(!this.isNewMode){
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe({
next: ticketData => {
this.ticket = ticketData; //<--really you needn't use ticketData
//unless you want compare old and new Values
//here you make the patchValue
this.form.patchValue(ticketData);
})
}
(you can also put in ngOnInit instead of ngAfterViewInit)
Update another aproach to the "clasic" problem to create a component to edit/create an element.
If you has a function like
getForm(data:TicketModel=null){
data=data || {} as TicketModel
return new FormGroup({
id: new FormControl(data.id,Validators.required),
field1: new FormControl(data.field1,Validators.required),
field2: new FormControl(data.field2,Validators.required),
field3: new FormControl(data.field3,Validators.required),
field4: new FormControl(data.field4,Validators.required)
})
}
You can in ngOnInit make some like
ngOnInit(){
this.id = this.route.snapshot.params['id'];
this.isNewMode = !this.id;
if (this.isNewMode)
this.Form=this.getForm()
else
{
this.sub = this.dataService.getById(this.id)
.pipe(first())
.subscribe(res=>{
this.Form=this.getForm(res)
})
}
}

Related

How to extract only certain fields from Http response object

I'm fairly new to Angular and I'm trying to get only certain values from the Http response object.
In my service, I'm doing a get request to fetch weather data for a given city like so:
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string){
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`);
}
}
Now, in the component, I'm logging out the response like so:
ngOnInit(): void {
this.cityService.getCity('lucija').subscribe(data => {
console.log(data)
});
}
The response itself it's just one object with many fields (also nested ones), which most of them I do not need.
I've also set up an interface where I would like to "save" in those certain response fields:
export interface City {
name: string;
description: string;
icon: string;
main: object;
search: string;
}
How can I do that? Cheers!
EDIT:
Based on the answer below I got 2 errors, which I resolved like so:
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res: any) => <City>{
name: res.name,
description: res.weather[0].description,
icon: `http://openweathermap.org/img/w/${res.weather[0].icon}.png`,
main: res.main,
search: name
}));
One solution could be to map the object in your service. Then your service will return the City Object.
export class CityService {
private baseUrl = 'https://api.openweathermap.org';
constructor(private http: HttpClient) { }
getCity(name: string): Observable<City>{
return this.http.get(`${this.baseUrl}/data/2.5/weather?q=${name}&appid=${environment.weatherApiKey}`)
.pipe(map((res) => { return { name: res.cityName }; }); // only added name as example. In the final code map all the values to the correct City field.
}
}
If you do not want your service to always return the City object you can do this mapping in your component.

BehaviorSubject issue, next() not working

I'm trying to use an angular service to store and communicate data through observable.
Here is my service
public _currentLegal = new BehaviorSubject({
name: 'init',
_id: 'init',
country: 'init',
city: 'init',
});
readonly currentLegal$ = this._currentLegal.asObservable();
constructor(private http: HttpClient) {
}
setCurrentLegal(legal) {
const legaltest = {
name: 'test',
_id: 'test',
country: 'test',
city: 'test',
}
console.log('emitting next value from', legal.name)
this._currentLegal.next({ ...legaltest });
}
I got a component that call setCurrentLegal, the console.log is triggered and correct. I then navigate to another component which subscribe to the currentLegal$ observable and the value is still the same (init) !
I tried accessing the value directly by setting public type and using getValue(), same.
I tried duplicating the object as to not pass a reference, did not change anything.
Here is my subscripting component ngOnInit :
ngOnInit(): void {
this.legalToUpdate = this.legalService.currentLegal$.subscribe((legal) => {
console.log(legal)
})
}
What's wrong here ?
Thanks
You have this behavior because you use it in different modules and it is created a different instances.
Put your service to be provide in root:
#Injectable({ providedIn: 'root' })
export class LegalService implements HttpInterceptor { }

Validate forms using external services

I'm trying to validate an Angular form using an external service but I'm getting a cannot read property of undefined error.
I've created a simple form in my component:
this.myForm = this.fb.group({
username: ['', [this.validator.username]],
});
From there, I'm calling my username method:
#Injectable()
export class ValidatorService {
constructor(private auth: AuthService) {}
username(input: FormControl): {[key: string]: any} {
return { userInvalid: this.auth.validate(input.value) };
}
}
My ValidatorService, then, calls a method that checks the server if that username exists:
#Injectable()
export class AuthService {
validate(username: string): boolean {
return username !== 'demo';
}
}
I'm getting the following error: Cannot read property 'auth' of undefined. Any ideas, please?
Live demo
username method is executed like a function not like a method of ValidationService so you're loosing context this.
Function.prototype.bind method should help you:
username: ['', [this.validator.username.bind(this.validator)]],

Angular - Re-Populate form inputs from service

I have a basic angular component that allows some one to edit the details of a user once they go to their profile and click on "edit".
Component:
export class EditUserComponent implements OnInit {
// Define our vars
user: Users[];
editUserForm: FormGroup;
message: {};
displayMessage = false;
userID: number;
errorMessage: any = '';
constructor(
private fb: FormBuilder,
private _userService: UserService,
private activatedRoute: ActivatedRoute
) {
}
ngOnInit(): void {
// Get the userID from the activated route
this.activatedRoute.params.subscribe((params: Params) => {
this.userID = params['id'];
});
// Call our service and pass the UserID
this._userService.getUser(this.userID)
.then(res => {
this.user = res;
this.createForm();
});
}
// Generate the form
createForm() {
this.editUserForm = this.fb.group({
QID: ['', Validators.required],
favoriteColor: [''],
favoriteNumber: [''],
favoriteActor: ['']
});
}
}
Service:
// Fetch a single user
getUser(userID: number) {
return this._http.post(this.baseUrl + '/fetchUser', { "userID": userID }, { "headers": this.headers })
.toPromise()
.then(res => res.json())
.catch(err => { this.handleError(err); });
}
Interface:
export interface Users {
RecordID?: number;
QID: string;
favoriteColor?: string;
favoriteNumber?: number;
favoriteActor?: string;
}
I am trying to pass the values to my formGroup but I am having trouble figuring out how to access the values.
I assumed I could do something like this where I could access the user model and select a property from it but that is throwing an undefined error.
Would I pass the values here in the form group or bind them to the elements directly somehow? I am receiving the data back from the service just fine, just not sure how to get each of the values back to their respective fields.
createForm() {
this.editUserForm = this.fb.group({
QID: [this.user.QID, Validators.required],
favoriteColor: [''],
favoriteNumber: [''],
favoriteActor: ['']
});
}
If I understand correctly ... this is what my code looks like:
onProductRetrieved(product: IProduct): void {
if (this.productForm) {
this.productForm.reset();
}
this.product = product;
// Update the data on the form
this.productForm.patchValue({
productName: this.product.productName,
productCode: this.product.productCode,
starRating: this.product.starRating,
description: this.product.description
});
this.productForm.setControl('tags', this.fb.array(this.product.tags || []));
}
I'm using patchValue for the values and setControl for the array.
OR
Since you are creating the form after retrieving the data, you could do something like this:
createForm() {
this.editUserForm = this.fb.group({
QID: [this.user.QID, Validators.required],
favoriteColor: [this.user.favoriteColor],
favoriteNumber: [this.user.favoriteNumber],
favoriteActor: [this.user.favoriteActor]
});
}
AND just to be complete ... each input element needs a formControlName property like this:
<input class="form-control"
id="productNameId"
type="text"
placeholder="Name (required)"
formControlName="productName" />
<span class="help-block" *ngIf="displayMessage.productName">
{{displayMessage.productName}}
</span>
</div>
You can find a complete working example here: https://github.com/DeborahK/Angular2-ReactiveForms
Bind a submit event to your form, then use this.editUserForm.value to access the data from the form.
In the component template:
<form [formGroup]="editUserForm" (submit)="saveIt()">
In the Component:
saveIt() {
if (this.editUserForm.dirty && this.editUserForm.valid) {
alert(`Number: ${this.editUserForm.value.favoriteNumber} Actor: ${this.editUserForm.value.favoriteActor}`);
}
}

Angular 2 Array printed on console but can't print object property on screen

I have the following method in a service I've created:
getPost(nid: string): Observable<Post[]>{
let url = "http://test.co.uk/api/v1/basic/" + nid;
return this.http.get(url, {headers: this.headers}).map(res => res.json() as Post).catch(err => {
return Observable.throw(err);
});
}
And this is the class of my component:
export class PostDetailComponent implements OnInit {
posts: Post[] = [];
post: Post = new Post();
constructor(
private route: ActivatedRoute,
private postService: PostService
) { }
ngOnInit() {
this.route.params.switchMap((params: Params) => {
let nid = params ['nid'];
return this.postService.getPost(nid); }).subscribe(res => {
console.log(res)
this.post = res as Post;
}, err =>{
console.log(err);
});
}
}
The JSON feed looks like this(yes one object in the array):
[
{
"nid":"3",
"title":"When Unity meets Vuforia",
"body":"<p>Unless you have been living under a rock in the past 7 - ...",
"uid":"admin",
"path":"\/node\/3",
"field_article_image":"http:\/\/test.co.uk\/sites\/default\/files\/when-unity-meets-vuforia_0.jpg?itok=BGYaotay"
}
]
So in my template, if I print {{post}} I get [object Object] on the screen.
If I print {{post | json}} I get the row JSON feed.
And finally, if I print {{post.title}} or {{post?.title}} I don't get anything.
I also have a class Post that is looking like this:
export class Post{
constructor(
public nid?: string,
public title?: string,
public body?: string
public image?: string
){
}
}
Any hints?
You are assigning an array into what should be a single object. Copy the first element of the array into the post variable
this.post = res[0] as Post
Side note: It's incorrect to assign a raw object to a class instance. In this case, your this.post.constructor won't exist and this.post instanceof Post == false.
You could do Object.assign(this.post, res[0]) but you may need to clear existing properties if not all properties are always present.
I prefer to define object shapes as interfaces instead, then you would not have that problem because all the interface information is removed at runtime, whereas a class does emit some code instead of just doing static type checks at compilation time

Categories

Resources