typescript 创建具有依赖关系的类的新实例,不了解工厂提供者

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/38472103/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-10-21 03:40:26  来源:igfitidea点击:

Create new instance of class that has dependencies, not understanding factory provider

angulartypescriptdependency-injection

提问by Beamer180

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.

我已经为此工作了一段时间,似乎找不到足够清晰的答案来理解。我有一个 TestComponent,它使用 TestService 从服务器获取一组 TestModel。当我获取这些测试模型时,它只是一个 json 文件,服务器正在读取该文件并使用正确的 mime 类型发回该文件。从服务器获取测试模型后,我将它们放在一个简单的选择元素下拉列表中。选择测试模型后,它会在嵌套组件 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.

这一切都很好,并且运行良好。当我从服务器中提取数据时,我一直遇到问题。由于 JavaScript 没有运行时检查,我们无法自动将 JSON 从服务器转换为 typescript 类,因此我需要使用检索到的 JSON 手动创建 TestModel 的新实例。

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

好的,问题就在这里。我需要调用新的 TestModel 并为其提供依赖项,但它需要是 TestModel 的新实例。我希望 TestModel 能够将自身保存并更新回服务器,因此它依赖于来自 @angular/core 的 Http 并且它依赖于我制作的配置类,该类使用 opaqueToken CONFIG.I 注入无法弄清楚如何获取 TestModel 的新实例。这是初始文件

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

TestDetailComponent 模板

<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:

我试过使用 ReflectiveInjector 所以 TestService 变成了这样:

    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:

但后来我得到了错误:

enter image description here

在此处输入图片说明

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.

然后,如果我将 Http 添加到 ReflectiveInjector,我只会收到另一个连接后端错误,我假设它会继续进入依赖链,直到我们找到底部。

Sorry for the long post, any help would be appreciated!

抱歉,帖子很长,任何帮助将不胜感激!

回答by Günter Z?chbauer

You can provide a factory function. This is different from a simple useFactory: ...provider like

您可以提供工厂功能。这与简单的useFactory: ...提供程序不同,例如

{ 
    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 needto use DI. You already inject Httpand CONFIGinto your TestService. You can just

但是您不需要使用 DI。您已经将Http和注入CONFIG到您的TestService. 你可以

@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 TestModelfrom resultfor example by passing the JSON to the constructor and initialize the members of TestModelfrom the passed JSON.

在任何情况下,你需要提供一些方法来初始化TestModelresult通过传递JSON的构造函数,例如和初始化的成员TestModel从通过JSON。

See also Angular2: How to use multiple instances of same Service?

另请参阅Angular2:如何使用同一服务的多个实例?

回答by Alexander Leonov

First of all, you are mixing two separate concerns here: one is holding data, which is your TestModel's concern, and another is saving that data, which isn't. This second concern should be implemented in TestService instead, it's its concern to talk to the server, so let it do its job.

首先,您在这里混合了两个单独的关注点:一个是保存数据,这是您的 TestModel 关注的问题,另一个是保存该数据,这不是。第二个关注点应该在 TestService 中实现,它关注与服务器的对话,所以让它完成它的工作。

Then, angular injectables are intended to be singletons. Quite obvious that data objects are not singletons, so you should not inject them through DI. What registered with DI is intended to be a service working with data objects, not data objects themselves. You can operate data objects directly or create some factory service which will create them for you being itself a singleton. There are plenty of ways to achieve that without DI.

然后,角注入旨在成为单身人士。很明显,数据对象不是单例,所以你不应该通过 DI 注入它们。向 DI 注册的内容旨在成为处理数据对象的服务,而不是数据对象本身。您可以直接操作数据对象或创建一些工厂服务,这些服务将为您自己创建它们作为单例。有很多方法可以在没有 DI 的情况下实现这一目标。

You can find more details about angular2 DI here. It's pretty long but luckily not very complicated.

您可以在此处找到有关 angular2 DI 的更多详细信息。它很长,但幸运的是不是很复杂。

回答by Julien

Thanks to everyone above, This is a working plunker I used. Hope it helps

感谢上面的每一个人,这是我使用的一个工作plunker。希望能帮助到你

http://plnkr.co/edit/NxGQoTwaZi9BzDrObzyP

http://plnkr.co/edit/NxGQoTwaZi9BzDrObzyP

import {Component, NgModule, VERSION, Injectable, Inject} from '@angular/core'
import {BrowserModule} from '@angular/platform-browser'
import {HttpClient} from '@angular/common/http'
import {HttpModule} from '@angular/http'

@Injectable()
export class HttpService{

  token = 'hihaa';
 constructor(){
 } 

 myFunction(value){
 console.log(value)

 }
}


export class Country{
  constructor(value,public httpService: HttpService){

    console.log(value,this);
  }

  classes(){

    this.httpService.myFunction('BGGGG')
  }
}


@Component({
  selector: 'my-app',
  template: `
    <div>
      <h2>Hello {{name}}</h2>
    </div>
  `,
})
export class App {
  name:string;
  country:any;

  constructor(
    @Inject('CountryFactory') countryFactory
    ) {
    this.name = `Angular! v${VERSION.full}`;
    this.country = countryFactory(3);
    this.country.classes();
  }
}

export let CountryProvider = { provide: 'CountryFactory',
    useFactory: (httpService) => {
      return (value) =>{
        return new Country(value,httpService)
      };
    },
    deps: [HttpService]
  }

@NgModule({
  imports: [ BrowserModule,HttpModule ],
  declarations: [ App ],
  bootstrap: [ App ],
  providers: [
    HttpService,
    CountryProvider

  ]
})
export class AppModule {}