I've been working on this for a while now and can't seem to find an answer clear enough to understand. I have a TestComponent that grabs an array of TestModels from a server using TestService. When I grab these test models it is just a json file that the server is reading and sending back with the correct mime type. Once the test models are grabbed from the server, I put them in a simple select element drop down. When a test model is selected it display the selected test model in a nested component, TestDetailComponent.
That is all well and good and is working fine. I keep running into issues when I am pulling in the data from the server. Since JavaScript has no runtime checking we can't automatically cast the JSON from the server to a typescript class so I need to manually create a new instance of the TestModel with the retreived JSON.
Okay so here is the problem. I need to call new TestModel and give it its dependencies but it needs to be a new instance of TestModel. I want the TestModel to be able to save and update itself back to the server so it has a dependency on Http from @angular/core and it has a dependency on a config class I made that the angular injects with an opaqueToken, CONFIG.I can't figure out how to get new instances of TestModel. Here are the initial files
TestComponent:
import { Component, OnInit } from '@angular/core';
import { TestService } from './shared/test.service';
import { TestModel } from './shared/test.model';
import { TestDetailComponent } from './test-detail.component';
@Component({
selector: "test-component",
templateUrl: 'app/test/test.component.html',
styleUrls: [],
providers: [TestService],
directives: [TestDetailComponent]
})
export class TestComponent implements OnInit {
tests: TestModel[] = [];
selectedTest: TestModel;
constructor(private testService: TestService) {};
ngOnInit() {
this.testService.getTestsModels().subscribe( (tests) => {
console.log(tests);
this.tests = tests
});
}
}
TestComponent template:
<select [(ngModel)]="selectedTest">
<option *ngFor="let test of tests" [ngValue]="test">{{test.testing}}</option>
</select>
<test-detail *ngIf="selectedTest" [test]="selectedTest"></test-detail>
TestDetailComponent:
import { Component, Input } from '@angular/core';
import { JsonPipe } from '@angular/common';
import { TestModel } from './shared/test.model';
@Component({
selector: 'test-detail',
templateUrl: 'app/test/test-detail.component.html',
pipes: [JsonPipe]
})
export class TestDetailComponent {
@Input() test;
}
TestDetailComponent template
<p style="font-size: 3em;">{{test | json}}</p>
TestModel
import { Injectable, Inject } from '@angular/core';
import { Http, Response, Headers, RequestOptions } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { CONFIG } from './../../config/constants';
@Injectable()
export class TestModel {
"testing": number;
"that": string;
"a": string;
constructor(private http: Http, @Inject(CONFIG) private config) {}
save(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
let body = JSON.stringify({
testing: this.testing,
this: this.that,
a: this.a
});
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.post(url, body, options)
.map( (response) => response.json() )
.map( (results) => {
results.map( (aggregate, current) => {
aggregate.push(<TestModel>current);
return aggregate;
}, new Array<TestModel>())
}).catch(this.handleError);
}
update() {
let url = this.config.apiUrl + "test";
let body = JSON.stringify({
testing: this.testing,
this: this.that,
a: this.a
});
let headers = new Headers({'Content-Type': 'application/json'});
let options = new RequestOptions({headers: headers});
return this.http.put(url, body, options)
.map( (response) => response.json() )
.map( (results) => {
results.map( (aggregate, current) => {
aggregate.push(<TestModel>current);
return aggregate;
}, new Array<TestModel>())
}).catch(this.handleError);
}
private handleError(err): Observable<any> {
let errMessage = err.message ? err.message : err.status ? `${err.status} - ${err.statusText}` : 'Server Error';
return Observable.throw(new Error(errMessage));
}
}
Test Service
import { Injectable, Inject } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { CONFIG } from './../../config/constants';
import { TestModel } from './test.model';
@Injectable()
export class TestService {
constructor(private http: Http, @Inject(CONFIG) private config) {}
getTestsModels(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
return this.http.get(url)
.map( (response) => response.json() )
.map( (results) => {
return results.map( (current) => {
return <TestModel>current; // <<<--- here is the error
})
})
.catch(this.handleError);
}
private handleError(err): Observable<any> {
let errMessage = err.message ? err.message : err.status ? `${err.status} - ${err.statusText}` : 'Server Error';
return Observable.throw(new Error(errMessage));
}
}
I have tried using the ReflectiveInjector so TestService becomes this:
import { Injectable, Inject, ReflectiveInjector } from '@angular/core';
import { Http, Response } from '@angular/http';
import { Observable } from 'rxjs/Rx';
import { CONFIG } from './../../config/constants';
import { TestModel } from './test.model';
@Injectable()
export class TestService {
constructor(private http: Http, @Inject(CONFIG) private config) {}
getTestsModels(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
return this.http.get(url)
.map( (response) => response.json() )
.map( (results) => {
return results.map( (current) => {
return ReflectiveInjector.resolveAndCreate([TestModel]).get(TestModel);
})
})
.catch(this.handleError);
}
private handleError(err): Observable<any> {
let errMessage = err.message ? err.message : err.status ? `${err.status} - ${err.statusText}` : 'Server Error';
return Observable.throw(new Error(errMessage));
}
}
But then I just get the error:
Then if I add Http to the ReflectiveInjector I just get another connection backend error and I am assuming that would keep going to the dependency chain till we found the bottom.
Sorry for the long post, any help would be appreciated!
You can provide a factory function. This is different from a simple useFactory: ...
provider like
{
provide: 'TestModelFactory',
useFactory: () => {
return (http, config) => {
return new TestModel(http, config);
};
},
deps: [Http, CONFIG];
}
and then use it like
@Injectable()
export class TestService {
constructor(@Inject('TestModelFactory' testModelFactory) {}
getTestsModels(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
return this.http.get(url)
.map( (response) => response.json() )
.map( (results) => {
return results.map( (current) => {
let tm = testModelFactory();
tm.xxx // assign data
})
})
.catch(this.handleError);
}
}
You can also support per instance parameters like
{
provide: 'TestModelFactory',
useFactory: (json) => {
return (http, config) => {
return new TestModel(http, config, json);
};
},
deps: [Http, CONFIG];
}
and then use it like
@Injectable()
export class TestService {
constructor(@Inject('TestModelFactory' testModelFactory) {}
getTestsModels(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
return this.http.get(url)
.map( (response) => response.json() )
.map( (results) => {
return results.map( (current) => {
let tm = testModelFactory(result);
})
})
.catch(this.handleError);
}
}
But you don't need to use DI. You already inject Http
and CONFIG
into your TestService
. You can just
@Injectable()
export class TestService {
constructor(private http: Http, @Inject(CONFIG) private config) {}
getTestsModels(): Observable<TestModel[]> {
let url = this.config.apiUrl + "test";
return this.http.get(url)
.map( (response) => response.json() )
.map( (results) => {
return results.map( (current) => {
return new TestModel(http, config);
})
})
.catch(this.handleError);
}
private handleError(err): Observable<any> {
let errMessage = err.message ? err.message : err.status ? `${err.status} - ${err.statusText}` : 'Server Error';
return Observable.throw(new Error(errMessage));
}
}
In every case you need to provide some way to initialize TestModel
from result
for example by passing the JSON to the constructor and initialize the members of TestModel
from the passed JSON.
See also Angular2: How to use multiple instances of same Service?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With