I have a method which makes a call to Angular's $resource. I am trying to test the return value of this method. This method should be simple enough to test.
Method Code
getCountries(): returnTypeObject {
var countries = this.$resource(
this.apiEndpoint.baseUrl,
{
'get': {
method: 'GET', isArray: false
}
});
return countries;
}
Unit Test
describe('module', (): void => {
beforeEach((): void => {
angular.mock.module('ngResource');
});
describe('getCountries...', (): void => {
it("should...", inject(
['Builder', (Builder: IBuilder): void => {
Builder.getCountries().get(
(value: any) => {
console.log(value);
}, (error: any) => {
console.log(error);
});
}]));
});
});
Now in the above code when I run my test the none of the callbacks ever get called. Neither "value" nor "error" ever get printed. I've been looking for a solution for quite a while now but haven't been able to find anything.
Keep in mind that I do not want to mock $resource, instead I want to test what is returned by getCountries().get() and then I want to use the callbacks to test the return value.
Related
I have the following function that traverse the tree-like object and it working fine so far.
const traverse = async (menuInputs: MenuInput[], parent: Menu = null) => {
for (const input of menuInputs) {
const entity = toEntity(input, parent);
const parentMenu = await this.menuService.create(entity).toPromise();
if (isEmpty(input.children)) {
continue;
}
await traverse(input.children, parentMenu);
}
};
My question is how can i invoke method this.menuService.create(entity) that is actually return an Observable<Menu> without convert it to Promise, are there any RxJS way of doing this ?
I've got something like this for recursive observable calls using the HttpService. You can probably work with something similar.
#Injectable()
export class CharacterReaderApiService implements CharacterReader {
private characters: SwapiCharacter[] = [];
constructor(private readonly http: HttpService) {}
async getCharacters(): Promise<Character[]> {
console.log('Querying the API for character data...');
return this.callUrl('https://swapi.dev/api/people')
.pipe(
map((data) => {
return data.map(this.mapPerson);
}),
tap(async (data) =>
promises.writeFile(join(process.cwd(), characterFile), JSON.stringify(data)),
),
)
.toPromise();
}
private callUrl(url: string): Observable<SwapiCharacter[]> {
return this.http.get<SwapiResponse>(url).pipe(
map((resp) => resp.data),
mergeMap((data) => {
this.characters.push(...data.results);
return iif(() => data.next !== null, this.callUrl(data.next), of(this.characters));
}),
);
}
private mapPerson(data: SwapiCharacter): Character {
return {
name: data.name,
hairColor: data.hair_color,
eyeColor: data.eye_color,
gender: data.gender,
height: data.height,
};
}
}
The important thing is keeping a running array of the values so that they can be referenced later on.
By using mergeMap with iif we're able to recursively call observables and work with the result as necessary. If you don't like the idea of a class variable for it, you could make the running array a part of the callUrl (or similar) method's parameters and pass it on as needed.
I'd strive to change toEntity() and menuService.create() to receive the list of children rather than the parent so I could use forkJoin:
const buildMenu = (input: MenuInput) =>
forkJoin(
input.children.map(childInput => buildMenu(childInput, menu))
).pipe(
flatMap(childMenus => this.menuService.create(toEntity(input, childMenus)))
);
const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
return new Proxy(obj, handler);
So I have this code that I copied from a gist on the internet, and it seems to work. I understand how the trap works, but I don't get how the proxy makes a return of a promise act like a synchronous value. My colleague is afraid this code has a race condition. Are there any race conditions? what's actually happening under the hood? do I need to improve this code any to make it safe?
code is written in typescript and running on nodejs 10.
It doesn't look like this code makes access synchronous at all. It looks like it serves to make any methods on the promise payload available on the promise, but they will still have deferred execution when invoked.
For example, given the following API:
interface Bird {
speak(): void;
}
function getBird(): Promise<Bird> {
return new Promise((resolve) => {
setTimeout(() => resolve({ speak() { console.log('CAW'); } }, 1000);
});
}
function proxyPromise<T>(promise: Promise<T>) {
// Your promise proxying code here
}
The proxyPromise code would allow you to call methods on the proxied object, and defer them until the promise is resolved:
const proxiedBird = proxyPromise(getBird());
console.log("Fetching Bird")
proxiedBird.speak;
console.log("Told it to speak");
This code would execute fine, but it won't make the speak() operation run synchronously--it will still wait for the getBird() promise to resolve. So in output you would see:
Fetching Bird
Told it to Speak
CAW
The code snippet you have would do nothing to support fields or methods with parameters on the proxied object.
You could describe its safe behavior with some typescript typing:
type MethodProxy<T> = {
[idx in keyof T]: T[idx] extends () => infer U ? Promise<U> : never;
}
function proxyPromise<T>(promise: Promise<T>): MethodProxy<T> {
const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
return new Proxy(promise, handler);
}
Tricky question #xenoterracide. But answering you, actually, this doesn't work synchronously at all, basically, you turned every obj property to be accessed only asynchronously, and if the property is not a function, it throws an error.
Under the hood, you are only trapping a promise get properties, and in the trap, waiting for the promise to resolve and execute the property (function) in this value resolved by the promise.
I simulate it in this playground:
const handler: ProxyHandler<any> = {
get: (target: Promise<any>, prop: string, receiver: any) => {
return target.then((o) => {
return o[prop].apply(o);
});
},
};
const obj = {
a() {
return 'Hi'
},
b: 5
}
const proxy = new Proxy(Promise.resolve(obj), handler);
//both prints promises
console.log(proxy.a) // after resolved, return 'Hi'
console.log(proxy.b) // error
This proxy approach could be useful if you don't want to resolve the promise itself. But you would need to await every property, and take care of not function ones.
I am developing angular application where I need apply following mechanism:
My view has 2 parts (list of items and detail if selected item). User can click on some item, and next service fetch additional data for that item and show them in detail view. Also I want select first item automatically on start if is available.
Here is my service:
#Injectable()
export class ItemService {
private url: string;
private itemSource = new BehaviorSubject<Item>(null);
selectedItem = this.itemSource.asObservable();
constructor(private http: HttpClient) {
this.url = 'http://localhost:8080/api/item';
}
getItems(): Observable<Item[]> {
let observable = this.http.get<Item[]>(this.url)
.map(items => items.map(item => {
return new Item(item);
}));
return observable;
}
selectItem(item: Item) {
return this.http.get<Item>(this.url + '/' + item.id)
.map(item => new Item(item))
.subscribe(t => this.itemSource.next(t));
}
}
in detail component I am subscribing selected item like this:
ngOnInit() {
this.itemService.selectedItem.subscribe(item => this.selectedItem = item);
}
and following code is from my component where I displayed list of items. I also want set selected item after data are subscribed but my code isn't works. I am iterating items[] property in html template and data are showed, but when I access to this array after I subscribed data I got undefined. Can you please fix my code? Thanks!
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
function () {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
Your problem is that you are not using an arrow function for the complete callback in your call to subscribe. As you see, you are using arrow functions for next and error.
When you define a new function with function(...) {...} you're creating a new context, and so the this keyword changes its meaning. The difference between arrow function and normal functions (besides being more elegant, in my opinion), is that arrow functions do not define a new context for this, and so the meaning of that keyword is the same as in the context they are defined. So, in your next and error callbacks, this is your component, but in your call to complete, this is, most surely, a reference to window, which does not have an items property, hence the undefined.
Change your code to:
public items = [];
constructor(private itemService: ItemService) { }
ngOnInit() {
this.itemService.getItems()
.subscribe(
data => this.items = data,
err => console.log(err),
() => {
console.log('selected data', this.items); // this prints undefined
if (this.items && this.items.length) {
this.itemService.selectedItem(this.items[0])
}
});
}
I imagine you used the function keyword there because that function had not arguments, but you can express that with the syntax () => expression, or () => {...}
data => this.items = data, after all, is a simpler and more elegant way of writing
(data) => { return this.items = data; }
I have created a service for google API and stacked at the promise response. Let me show you my code:
getPromise: Promise<any>;
loadSheets: Array<any>;
constructor(public _checkAuthApiService: CheckAuthApiService) { }
ngOnInit() {
if (this._checkAuthApiService) {
this._checkAuthApiService.checkAuth().then(res => {
if (res && !res.error) {
this.loadSheetsFunc();
}
});
}
//setTimeout(function(){},1000); //When i add this it works :(
}
loadSheetsFunc = () => {
this.getPromise = new Promise((resolve: any, reject: any) => {
resolve(this._checkAuthApiService.loadSheetsApi())
});
this.getPromise.then((res: any) => this.sheetsResults(res));
};
sheetsResults = (res: any) => this.loadSheets = res.result.values;
not sure what i am missing but when i add seTimeout within ngOnInit ti works i get the data i want on the view. Can someone help me with this code or perhaps suggest me a better way using Observables. Thank you in advance.
The setTimeout() causes a full Angular2 change detection cycle, which is why this makes Angular2 recognize the updated property.
That Angular2 doesn't recognize the update without setTimeout() indicates an issue with polyfills (perhaps loading order).
I would make a few changes to your code
loadSheets: Array<any>;
constructor(public _checkAuthApiService: CheckAuthApiService) { }
ngOnInit() {
if (this._checkAuthApiService) {
this._checkAuthApiService.checkAuth().then(res => {
if (res && !res.error) {
this.loadSheetsFunc();
}
});
}
}
loadSheetsFunc() {
this._checkAuthApiService.loadSheetsApi()
subscribe(val => this.sheetsResults(res));
}
sheetsResults(res: any) {
this.loadSheets = res.result.values;
}
I don't know though what loadSheetsApi() returns (assuming Observable).
Success gets called in a successful factory response in the then callback:
This doesn't work, it cannot find response:
this.storeFactory.getSafes().then(success(response), failure());
How to I pass the response to the success function correctly?
var success = (response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
The long hand version works fine, but I feel like it is very messy.
this.storeFactory.getSafes().then((response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
How to I pass the response to the success function correctly?
You don't. You pass the success function to the then method, then the promise will pass the result value to your success function. That's how callbacks work.
All you need to do is declare response as a paramter of your function. You must not call the function yourself - you only should pass it as a callback:
this.storeFactory.getSafes().then(success, failure);
Also you will need to define the functions before you pass them to then. If you only declare them, and pass undefined values to then, they will be ignored. Use
var success = (response: any): void => {
scope.safes = response.data.safes;
localStorage.setItem('safeCount', scope.safes.length);
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
};
var failure = (): void => {
this.$http.get('/app/shared/mocks/tableError.html', { cache: this.$templateCache }).success((tplContent): void => {
element.replaceWith(this.$compile(tplContent)(scope));
});
}
this.storeFactory.getSafes().then(success, failure);
However, arrow functions are actually supposed to be defined inline, without assigning them to a specific variable. (You called this the "long hand version" in your question, even if it's actually shorter). Just use that and you won't face these problems.
In general, I would recommend to avoid defining functions in variable assignments completely. If you need a variable, just use a declaration instead (Typescript syntax should not vary much).
I can't speak to the ES2015 syntax or Typescript, however the way you're passing back your success callback looks suspect.
instead of
this.storeFactory.getSafes().then(success(response), failure());
you should use
this.storeFactory.getSafes().then(success, failure);
The callback of your AJAX call also needs to use arrow functions:
this.$http.get('/app/dashboard/safes/safes.html', { cache: this.$templateCache }).success((tplContent) => {
element.replaceWith(this.$compile(tplContent)(scope));
});