AngularJs 2: How to debug a service call? (es6 syntax) - javascript

THE SITUATION:
I am just learning Angular2. I wanted to debug the call to a service.
The service has been properly called, as I can see in the view.
I also want to log to content of variable that hold the result, but it is not working as i would except.
THE SERVICE:
export class ListService
{
getList()
{
return Promise.resolve(LIST);
}
}
THE CALL (from the list component):
export class ListComponent implements OnInit
{
list: Item[];
constructor(
private _router: Router,
private _listService: ListService
) { }
getList()
{
this._listService.getList().then(list => this.list = list);
}
ngOnInit()
{
this.getList();
}
}
LOG ATTEMPT 1:
list is the variable containing the list of items. In the view it properly show the content. So i would expect to log it and see its content.
getList()
{
this._listService.getList().then(list => this.list = list);
console.log(list)
}
But when i get this error instead:
EXCEPTION: ReferenceError: list is not defined in [null]
LOG ATTEMPT 2:
Trying to get the correct syntax:
getList()
{
this._listService.getList().then(list => this.list = list);
console.log(this.list)
}
There are no errors now. But in the console it shows undefined.
But the service has already been called. So it should contain the content
THE QUESTION:
Considering that i am using Angular2 - Ecmascript 2016
What is the proper syntax to log the call of a service?
Thank you!

In fact this.list is set within the callback registered in the then method. So you should use something like that:
getList()
{
this._listService.getList().then(list => {
this.list = list;
console.log(list);
console.log(this.list);
});
}
If you put the console.log(this.list); right the promise, the result won't be probably there so this.list probably doesn't set...

Related

Angular calling an Array from a service method() to my main component ;

In my api-service.ts
i have an array which contains some data in an Array.
public getData():Observable<any[]> {
return Observable.toString[ObsResult];
}
Then in my main component i am trying to call getData() method to display data in
main.componenet.html
service.getData().subscribe({result => console.log (result)});
I get the error TypeError: Cannot read property 'subscribe' of undefined
I am guessing this line is wrong but i am not sure what to put here
Observable.toString
There are multiple approaches you could use to solve the problem.
Method 1: More Native implementation
public getData(): Observable < any[] > {
return new Observable(observer => {
observer.next(ObsResult);
observer.complete();
});
}
Method 2: Angular (rxjs) Implementation
public getData(): Observable < any[] > {
return of(ObsResult);
}
If you have static data
example: const ObsResult = [1,2,3,4,5];
You can use
public getData():Observable<any[]> {
return Observable.of(ObsResult);
}
then you can subscribe to
service.getData().subscribe({result => console.log (result)});

Angular - Using a service property set by callback in another component

I am trying to use a DataService property myData that is waiting for callback. But it is undefined when I call in DataComponent. How can I access and use it there?
export class DataService {
public myData;
constructor(private http: HttpClient) {
this.load().then((data) => {
this.myData = data
})
}
load() {
return new Promise((resolve) => {
this.http.get('https://reqres.in/api/users').subscribe(
(res: any) => {
console.log(res.data)
resolve(res.data)
},
(error) => {
console.log(error);
}
)
})
}
}
export class DataComponent implements OnInit {
constructor(private dataService: DataService) {
this.prepareData();
}
prepareData() {
console.log(this.dataService.myData)
}
ngOnInit(): void {
}
}
Here is the source code: https://stackblitz.com/edit/angular-ivy-kbpdpo
You are running into a race condition since this is an asynchronous function.
This change works: https://stackblitz.com/edit/angular-ivy-vf3llg
Consider reading up on https://angular.io/guide/http
Personally, I just have services return raw data and manipulate it elsewhere, but if needed you can tap into the response as I have shown i the updated example.
This question and answer are probably really a duplicate of this question...
What are pipe and tap methods in Angular tutorial?
your load() method is asynchronous, that means that it can return the response after 2 hours, so it will execute your callback after 2 hours, and you are asking myData synchronously which means that you are asking it right now, so it won't work.
you have to wait until the answer is returned, in your code there is no chance to accomplish this, so either remove yourData field and just subscribe it into the component, or create BehaviorSubject and emit value to the component

Angular 2+ wait for subscribe to finish to update/access variable

I am having an issue with my variables being undefined. I am certain this is because the observable hasn't finished. Here is the part of my code in my .ts file that is causing the issue. (I'm placing the minimum code required to understand the issue. Also myFunction gets called from a click event in the HTML).
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
});
console.log(myVariable) --> undefined
}
}
So this piece of code calls a function in my service that returns some data from an API. The issue is that when I try to access the variable myVariable right outside of the subscribe function it returns undefined. I'm sure this is because the subscribe hasn't finished before I try to access myVariable
Is there a way to wait for the subscribe to finish before I try to access myVariable?
why not create a separate function and call it inside the subscription.
export class myClass {
myVariable: any;
myFunction() {
this.myService.getApi().subscribe(data => {
this.myVariable = data;
this.update()
});
this.update()
}
update(){
console.log(this.myVariable);
}
}
As you know subscriptions are executed when server return data but the out side of subscription code executed synchronously. That is why console.log outside of it executed. The above answer can do your job but you can also use .map and return observable as shown below.
let say you are calling it from s service
export class myClass {
myVariable: any;
// calling and subscribing the method.
callingFunction() {
// the console log will be executed when there are data back from server
this.myClass.MyFunction().subscribe(data => {
console.log(data);
});
}
}
export class myClass {
myVariable: any;
// this will return an observable as we did not subscribe rather used .map method
myFunction() {
// use .pipe in case of rxjs 6
return this.myService.getApi().map(data => {
this.myVariable = data;
this.update()
});
}
}

testing a service call in jasmine

I am trying to write a unit-test for a function that calls a service. But am running into the error: TypeError: undefined is not a constructor
What I am trying to test is a service call that, on success, sets the value of the variable 'cards'.
I've created the appropriate mock for the service (CardService), which you can see in the spec file below
test.component.spec.ts
class MockCardService extends CardService {
constructor() {
super(null); // null is the http in service's constructor
}
getCardDetails(): any {
return Observable.of([{ 0: 'card1' }, { 1: 'card2' }]);
}
}
describe('MyComponent', () => {
let component: MyComponent;
let mockCardService: MockCardService;
beforeEach(() => {
mockCardService = new MockCardService();
component = new MyComponent(
mockCardService // add the mock service that runs before each test
);
});
// The failing test :(
it('should set the card variable to the value returned by the service', () => {
spyOn(mockCardService, 'getCardDetails').and.callThrough();
// Act
component.ngOnInit();
component.updateCards(); // call the function I am testing
// Assert
expect(component.cards).toConTainText('Card1');
});
And the component file with the function I'm testing:
export class MyComponent implements OnInit {
public cards: CardModel[] = [];
constructor(
private cardService: CardService,
) {
}
ngOnInit() {
this.updateCards(); // call the update card service
}
updateCards(): void {
this.cardService.getCardDetails().subscribe(
(cardsDetails) => {
this.cards = cardsDetails;
},
(err) => {
// todo: error handling
console.log(err);
}
);
}
}
Whenever this test runs I recieve the error:
TypeError: undefined is not a constructor (evaluating 'Observable_1.Observable.of([{ 0: 'card1' }, { 1: 'card2' }])') (line 22)
getCardDetails
updateCards
ngOnInit
And I can't figure out what I'm doing wrong, why 'getCardDetals.subscribe' is undefined. The MockCardService class I provided doesn't appear to be working for some reason.
(note that this.cardService.getCardDetails() is defined, if I log it out in the component itself )
Any help would be very much appreciated.
Author here:
I'm still not sure what is going wrong. But I was able to fix this by changing class MockCardService extends CardService { to just let MockCardService, and using that variable throughout. Good luck to anyone who runs into this!
MockCardService.getCardDetails() should return an Observable, so you can run subscribe in the component.

Angular 2+ http service is being called, but request is not going out

I want to be able to instantiate a model class, but also give it access to services.
For example, say I have these endpoints:
/book/:id
/book/:id/author
I want to have a BooksService service to fetch a list a Book instance. I'd like the book instances to be instantiated with new, given a definition JSON through the constructor, while still being able to use Angular dependencies.
Example of what I want to do:
BooksService.getBook(1) // makes call to /book/1
.subscribe((book) => {
book.getAuthor() // makes call to /book/1/author
...
});
To accomplish this, I tried to use a factory to new an instance of a book model. I then pass in a reference to the Http injected dependency that the factory injects.
Here's what I have:
BooksService
#Injectable()
export class BookService {
constructor(
private http: Http,
private bookModelFactory: BookModelFactory
) {}
getBook(): Observable<BookModel> {
return this.http.get('http://localhost:3000/book/1')
.map((res) => {
return this.bookModelFactory.make(res.json().data);
});
}
}
BookModel, BookModelFactory
#Injectable()
export class BookModelFactory {
constructor (private http: Http) {}
make(def: object): BookModel {
var book = new BookModel(def);
book.http = this.http;
return book;
}
}
export class BookModel {
def: any;
http: Http;
constructor (def: object) {
this.def = def;
}
getAuthor() {
console.log('http', this.http);
this.http.get('http://localhost:3000/book/1/author');
}
}
When I try to use this code, I see the console log for the http object in book.getAuthor(). It exists, and I can see the get method on it. But it never makes the API request. Nothing in the network tab has anything about a call to /book/1/author. There are no errors. Simply put, nothing happens.
Why isn't the request being made when this.http.get('...') is being called in getAuthors()?
Thanks in advance.
Using Angular 4.
(Imports statements are removed for brevity)
2) If this is a good strategy... why isn't the request being made when this.http.get('...') is being called in getAuthors()?
Because no-one ever subscribed to the results of this call:
this.http.get('http://localhost:3000/book/1/author').subscribe(res => {
// do something with the results here
});
In angular if you do not subscribe to the results of the HTTP call, then this call will never be made.
Or maybe you want your getAuthor method to return an Observable<Author> so that it is the caller of this method that can subscribe to the results:
getAuthor(): Observable<Author> {
return this.http.get('http://localhost:3000/book/1/author').map(res => {
return res.json().data;
});
}
So that you can subscribe to it later:
BooksService.getBook(1) // makes call to /book/1
.subscribe(book => {
book.getAuthor() // makes call to /book/1/author
.subscribe(author => {
// do something with the book author here
});
...
});
So remember, if you do not subscribe, the AJAX call will not be made.

Categories

Resources