I have been beating against this problem for a couple days now and cannot move forward.
I am trying to mock a database using some source JSON files that represent tables with foreign key relationships. Let us say that there is an entity A
and an entity B
, where A
has a many-to-one relationship with B
. In addition to requiring some additional cleanup, the JSON files only list primary keys for these relationships, so I have DTO wrappers RawA
and RawB
that I can map to A
and B
respectively once I get the data.
I am using the angular-in-memory-web-api, so I have set up my app.module.ts to use a custom service that allows me to retrieve the JSON files like so:
HttpClientInMemoryWebApiModule.forRoot(
InMemoryDataService,
{ apiBase: '/api', dataEncapsulation: false, passThruUnknownUrl: true }
)
In my app.component.ts, I temporarily have an ngOnInit()
method that test the construction of this database:
ngOnInit() {
let result = this.http.get('/api/A')
.subscribe(
next => { console.log('next:'); console.log(next); },
error => { console.log('error:'); console.log(error); },
() => console.log('complete')
);
}
As I understand this, the in-memory database should see this request, call createDb()
on my service, and subscribe to the Observable that my method returns, and then use the list of A
s I create in subscribe()
.
I cannot get this to work. I have tried many things, perhaps the most intuitive of which is:
// most imports omitted, but I am using
import { Observable } from 'rxjs/Observable';
import 'rxjs/Rx';
@Injectable()
export class InMemoryDataService implements InMemoryDbService() {
private static aUrl = './assets/data/a.json';
private static bUrl = './assets/data/b.json';
private http: HttpClient;
constructor( private injector: Injector ) {}
createDb( reqInfo?: RequestInfo ): {} | Observable<{}> | Promise<{}> {
this.http = this.injector.get(HttpClient);
return Observable.forkJoin(
this.http.get<RawA[]>(InMemoryDataService.aUrl),
this.http.get<RawB[]>(InMemoryDataService.bUrl)
).map(
result => {
let bs = (result[1] as RawB[]).map(raw => new B(raw));
let as = (result[0] as RawA[]).map(raw => new A(raw, bs));
return { A: as, B: bs };
}
);
}
}
The subscribe()
callbacks in ngOnInit()
never run. I have tried various combinations of mapping, subscribing, converting to Promises, and swearing, and I cannot anything better than either ngOnInit()
never doing anything or prompting the in-memory DB to return a 404.
I know that the forkJoin()
documentation warns that all the Observables need to return before it will produce any values, but as best I can tell, the two http.get()
calls should return if the wrapping plumbing subscribes to them. The following code successfully logs the database I expect (but it generates a 404, probably because the immediate call to api/A sees an empty array):
createDb( reqInfo?: RequestInfo ): {} | Observable<{}> | Promise<{}> {
this.http = this.injector.get(HttpClient);
let database: { A: A[], B: B[] } = { A: [], B: [] };
Observable.forkJoin(
this.http.get<RawA[]>(InMemoryDataService.aUrl),
this.http.get<RawB[]>(InMemoryDataService.bUrl)
).subscribe(
result => {
database.B = (result[1] as RawB[]).map(raw => new B(raw));
database.A = (result[0] as RawA[]).map(raw => new A(raw, database.B));
console.log(database);
return database;
}
);
return database;
}
// console logs {A: Array(100), B: Array(20)}, and inspection shows the field values are all correct
What am I missing to make this work? Also, is this legitimately tricky, or am I missing something obvious?
Looking at your code:
Observable.forkJoin(
this.http.get<RawA[]>(InMemoryDataService.aUrl),
this.http.get<RawB[]>(InMemoryDataService.bUrl)
).subscribe(
I suspect that since you hooked up all http requests and haven't create Db yet these requests will be always in status waiting.
In order to overcome it i would use something else to get json data.
For instance Fetch API:
createDb( reqInfo?: RequestInfo ): Observable<{}> | Promise<{}> {
this.http = this.injector.get(HttpClient);
return Promise.all([
fetch(InMemoryDataService.aUrl),
fetch(InMemoryDataService.bUrl)
])
.then((result: any) => {
return Promise.all(result.map(x => x.json()));
})
.then(
result => {
const bs = (result[1] as RawB[]).map(raw => new B(raw));
const as = (result[0] as RawA[]).map(raw => new A(raw, bs));
return { A: as, B: bs };
}
);
}
Another thing you need to change is api url
app.module.ts
{ apiBase: 'api/', dataEncapsulation: false, passThruUnknownUrl: true }
^^^^^^
component.ts
let result = this.http.get('api/A')
^^^^^^^^^
Or you can use the following pair:
{ apiBase: '/', dataEncapsulation: false, passThruUnknownUrl: true }
let result = this.http.get('/A')
The reason of this is how angular-in-web-memory-api
parses your url