I am new to Angular so I am having trouble figuring out how to form my questions for what I am trying to accomplish, but here it goes.
I have a component that is fetching a single user record from a service. I then want to display those user details on my UI. In other parts of my code, they have always been multiple records so I have used *ngFor and looped over the array of data. However, since this is just a single result, I am not too sure how to accomplish this.
Component:
import { Component, OnInit } from '#angular/core';
import { Router, ActivatedRoute, Params } from '#angular/router';
import { UserRecord } from '../shared/user-record.interface';
import { UserService } from '../shared/user.service';
#Component({
selector: 'app-view-record',
templateUrl: './view-record.component.html',
styleUrls: ['./view-record.component.css']
})
export class ViewRecordComponent implements OnInit {
private record: UserRecord[];
private errorMessage: any = '';
private loaded = false;
private RecordID: number; // User ID of who we are looking at
constructor(private _crudService: UserService,
private activatedRoute: ActivatedRoute) { }
ngOnInit() {
// Get the userID from the activated route
this.activatedRoute.params.subscribe((params: Params) => {
this.RecordID = params['id'];
});
// Call our service and pass the userID
this._crudService.getRecord(this.RecordID)
.then(res => {
this.record = this._crudService.record;
return this._crudService.getRecord(this.RecordID);
})
.then(res => {
console.log(this.record)
this.loaded = true;
})
.catch(err => { console.error(err); });
}
}
Service:
getRecord(userID: number) {
const headers: Headers = new Headers({
"Authorization": this._frameworkService.getSessionInfo().token
});
return new Promise((resolve, rejects) => {
this._http.post(this.baseUrl + '/fetchRecord', { "userID": userID }, { "headers": headers })
.map(res => res.json())
.subscribe((data) => {
if (data) {
this.record = data;
}
resolve(true);
});
});
}
Interface:
export interface UserRecord {
RecordID: number;
QID: string;
FavoriteColor?: string;
FavoriteNumber?: number;
FavoriteActor?: string;
MetaInsertUTC: string;
MetaUpdateUTC: string;
FirstName: string;
LastName: string;
NTID: string;
}
Service Result:
[
{
"RecordID":"55",
"QID":"Q00019204",
"FavoriteColor":"Blue",
"FavoriteNumber":"6",
"FavoriteActor":"Bob",
"MetaInsertUTC":"2017-06-29 18:47:01.750",
"MetaUpdateUTC":null,
"FirstName":"Jim",
"LastName":"Bobs",
"NTID":"bobby"
}
]
In my Component HTML, I have tried {{record.FirstName}} but receive the error of ViewRecordComponent.html:16 ERROR TypeError: Cannot read property 'FirstName' of undefined.
Since this isn't a set of data results, I don't see how *ngFor would be applicable in the use case.
I assumed that since my component is storing the data in the record object, I should be able to access that from the UI? The console.log shows all of the correct data points.
How would I reference the users FirstName in my component HTML? Hopefully I'm on the right path at least.
Your response seems to be an array with an object, so record.FirstName doesn't exist, but record[0].FirstName does.
And when it comes to the view, remember to use either the safe navigation operator or *ngIf so that you do not run into undefined issues like mentioned by DeborahK. Observable type error: cannot read property of undefined
Furthermore just some suggestion on how to handle http in Angular... I would do something like the following...
getRecord(userID: number) {
const headers: Headers = new Headers({
"Authorization": this._frameworkService.getSessionInfo().token
});
return this._http.post(this.baseUrl + '/fetchRecord', { "userID": userID }, { "headers": headers })
.toPromise()
.then(res => res.json()[0]) // get the object only
.catch(err => { console.error(err); });
}
and component:
this._crudService.getRecord(this.RecordID)
.then(res => {
this.record = res;
});
But that's totally up to you :)
Getting data from Http is asynchronous. This means that when the page is first displayed, the data is not yet there.
There are several ways to resolve this:
One option is to use the "?" (safe navigation) operator: {{record?.FirstName}} This better handles nulls. See this link for more information: https://angular.io/guide/template-syntax#the-safe-navigation-operator----and-null-property-paths
Another option is to use *ngIf around your HTML code. *ngIf='record'
So when your page is first displayed, it will not generate an error that record is not yet set. As soon as the data is retrieved, the binding will notice the change and update the UI appropriately.
Here is what one of my service methods look like:
getProducts(): Observable<IProduct[]> {
return this._http.get(this._productUrl)
.map((response: Response) => <IProduct[]> response.json())
.catch(this.handleError);
}
And here is the call to that service:
ngOnInit(): void {
this._productService.getProducts()
.subscribe(products => this.products = products,
error => this.errorMessage = <any>error);
}
Notice that the subscribe is in the component that calls the service, not in the service itself.
Related
Trying to make use of same API in 3 components, In order to avoid duplicate HTTP calls I can use shareReply() to cache the response and use it where ever I want from RxJs. So I did like below
api-service.ts
getUsers(): Observable<any> {
let headers = new HttpHeaders();
headers = headers.set('app-id', '63b691428f53f6370fc9eed6');
return this.http.get(this.url, { headers }).pipe(
map((resp) => {
return resp;
}),
shareReplay()
);
}
test1-component
data$!: Observable<any>;
constructor(private api: ApiService) {}
ngOnInit(): void {
this.loadTest1Data();
}
loadTest1Data() {
this.data$.subscribe({
next: (response) => {
console.log('Loading data for Component - 1', response);
},
error: (error) => {
console.log('Error While Loading data for Component - 1', error);
},
complete: () => {
console.log('Success');
},
});
}
test2-component (test3-component also use same code)
data$!: Observable<any>;
constructor(private api: ApiService) {}
ngOnInit(): void {
this.loadTest2Data();
}
loadTest2Data() {
this.data$.subscribe({
next: (response) => {
console.log('Loading data for Component - 2', response);
},
error: (error) => {
console.log('Error While Loading data for Component - 2', error);
},
complete: () => {
console.log('Success');
},
});
}
Error i got reproducing HERE - Stackblitz
Cannot read properties of undefined (reading 'subscribe')
Could someone tell me what went wrong and how to resolve it? or is there any other alternative approach is there? (apologies, I'm not allowed to use any third party state management tools)
Thanks in Advance for your valuable time
Your test components aren't defining data$ so this.data$.subscribe is doing the equivalent of undefined.subscribe, hence the error.
For the test components you can do
#Input() data$: Observable<any> = of();
and pass in via
<app-test1 [data$]="data$"></app-test1>
Note, it's more common to do
<app-test1 [data]="data$ | async"></app-test1>
so that the components receive the data directly and the AsyncPipe takes care of subscribing and unsubscribing.
I am trying to write unit test for this angular script:
export class DataService {
private csrfToken: string = '';
private isContentShow: BehaviorSubject<boolean> = new BehaviorSubject(true);
constructor(private http: HttpClient, private cookieService: CookieService) {
this.token = this.cookieService.get('token');
}
public createData(data: Data) {
try {
this.http.post( url,
data,
{
headers: new HttpHeaders({
'Content-Type': 'application/json',
'Authorization': this.token
})
})
.subscribe(
data => {
this.isContentShow.next(true);
},
err => {
this.showError();
},
() => console.log('Request Complete')
);
return true;
} catch {
this.showError();
}
}
public getIsContentShow(): Observable<boolean> {
return this.isContentShow.asObservable();
}
}
The test that I had so far and its running as expected.
it('#getIsContentShow should return value from observable',
(done: DoneFn) => {
service.getIsContentShow().subscribe(value => {
expect(value).toBe(true);
done();
});
});
However I am trying to write the test for createData() function
I am able to mock the HttpClient using HttpClientTestingModule however I don't know how to handdle the CookieService and token ?
Thanks
You can use spies to spy on the cookieService get method. This way, you can write your unit test to test the combinations of returns you say the cookieService can provide.
This link says that you can spy on the prototype of the method in order to handle it how you like in the constructor.
it(
"should call #getGeneralStats in the constructor",
inject(
[CookieService, HttpClient],
(cookieService: CookieService, http: HttpClient) => {
let mySpy = spyOn(cookieService, 'get').and.returnValue(<your value>);
dataService = new DataService(http, cookieService);
expect(mySpy).toHaveBeenCalled();
}
)
);
For you, this may depend on how you're writing your tests. The example shows the service being instantiated like new ServiceName, but it's also possible to use dependency injection to get the service. If you're using DI for the service you are testing, I'd have to research more how to do this (others please feel free to add your answer if you know how to do that)!
I'm fairly new to angular and got stuck at getting data from SpringREST which is at backend.
So scenario is:I'll be getting a JSON string from backend as POST(JSON data will be redirected to my hosted link of site as POST) and I need to catch that JSON string and display it on UI.
I'm not sure about the postMethod in dataservice.ts if it should be there.
I googled on stackoverflow and came up with below code which doesn't seem to work in my scenario:
Component.ts
import { MyDataService } from './services/my-data.service';
constructor(private posting: MyDataService
) {}
ngOnInit() {
this.posting.postMethod().subscribe(
(response => {
console.log(response)
}));
}
}
Data-service.ts
#Injectable()
export class MyDataService {
constructor(private http: Http)
{ }
postMethod(model: any ) {
return this.http.post("http ://", model)
.map(res => res.json());
}
}
As the error says, You need to pass the parameter to the service when invoking
this.posting.postMethod(model).subscribe(
(response => {
console.log(response)
}));
As i can see in your component.ts you are not passing the model as a parameter.
you need to pass the model as a parameter.
this.posting.postMethod(anyData).subscribe(
(response => {
console.log(response)
}));
If this is not the issue then please update us with the error you are getting.
This is the right way to define a function in the subscribe method:
ngOnInit() {
this.posting.postMethod(model).subscribe(
(response) => {
console.log(response)
});
}
I have this problem. When I am trying to subscribe to a data returned from service I get undefined when trying to log it.
This is my route, I have tested it with REST client and it works fine:
router.post('/liveAuction', (req, res, next) => {
const id = req.body.id;
Auction.getAuctionById(id, (err, liveAuction) => {
if (err){
res.json({
success: false,
message: "Something went wrong!"
});
console.log(err);
}
else {
res.json({
success: true,
message: "Auction retrieved!",
liveAuction
});
}
});
});
This is my method of getting data from mongoDB:
module.exports.getAuctionById = function(id, callback){
const query = {_id: id};
Auction.find(query, callback);
console.log(query);
}
This is my service:
getAuctionById(id):any{
let headers = new Headers();
headers.append('Content-Type', 'application/json');
return this.http.post('http://localhost:3000/auctions/liveAuction', {id: id}, {headers: headers})
.map(res => res.json());
}
And this is my component.ts:
import { Component, OnInit, ElementRef, Inject } from '#angular/core';
import { Observable } from 'rxjs/Rx';
import {Router, RoutesRecognized} from '#angular/router';
import { AuctionService } from '../../services/auction.service';
import { DataTransferService } from '../../services/data-transfer.service';
#Component({
selector: 'app-auction-details',
templateUrl: './auction-details.component.html',
styleUrls: ['./auction-details.component.css']
})
export class AuctionDetailsComponent implements OnInit {
liveAuction: any;
auction: any;
id: any;
constructor(
private auctionService: AuctionService,
private dataService: DataTransferService
) {
this.dataService.currentProduct.subscribe(auction => this.auction = auction);
this.id = this.auction._id;
console.log(this.auction._id);// I get the right id here.
}
ngOnInit() {
this.auctionService.getAuctionById(this.id).subscribe(auction => this.liveAuction = auction.liveAuction);
console.log(this.liveAuction);// Here I get undefined.
Also if I try to use get items of it, like this.liveAuction._id, I get "Cannot find property _id of undefined" error.
Could you help me understand what I am doing wrong? I have done similar procedure for my other component which I use as Mat-Dialog component with different service, but functionality is completely the same. I have compared them like three times already and everything looks same but here it doesn't work. Please suggest what I am doing wrong. Thanks!
export class PersonEditDetailsComponent implements OnInit,OnDestroy {
person: Person;
sub: any;
constructor(private peopleService: PeopleService,
private route: ActivatedRoute,
private router: Router) {
}
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
console.log('getting person with id: ', id);
this.peopleService
.get(id) // send http request
.subscribe(response => this.person = response); // get response value
});
}
//I want to access this.person values here.
}
I'm newbie to Angular2 JS. As above code, I just want to map the json response data to angular2 typescript object. How can I do this. It is getting 'response' value. But can not map to person object. I want to use response data from the outside
If you want to use your response, e.g in a method in your component, the easiest way would be to call that method from inside the subscription, after you have got your value to person:
ngOnInit() {
this.sub = this.route.params.subscribe(params => {
let id = Number.parseInt(params['id']);
console.log('getting person with id: ', id);
this.peopleService
.get(id)
.subscribe(response => {
this.person = response;
this.doSomething(); // call method here!
});
});
}
doSomething() {
console.log(this.person) // here value is available!
}
.subscribe(response => this.person = .json().data as Person); // get response value
In you PeopleService on your get use .publishLast().refCount() to cache the result/