【问题标题】:Create new instance of class that has dependencies, not understanding factory provider创建具有依赖关系的类的新实例,不了解工厂提供者
【发布时间】:2016-11-23 03:35:33
【问题描述】:

我一直在研究这个问题,但似乎无法找到足够清晰的答案来理解。我有一个 TestComponent,它使用 TestService 从服务器中获取一组 TestModel。当我抓取这些测试模型时,它只是一个 json 文件,服务器正在读取并使用正确的 mime 类型发回。从服务器获取测试模型后,我将它们放在一个简单的下拉选择元素中。选择测试模型后,它会在嵌套组件 TestDetailComponent 中显示选定的测试模型。

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

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

测试组件:

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

<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 模板

<p style="font-size: 3em;">{{test | json}}</p>

测试模型

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));

    }

}

测试服务

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));

    }

}

我尝试过使用 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));

    }

}

但后来我得到了错误:

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

抱歉发了这么长的帖子,如有任何帮助,将不胜感激!

【问题讨论】:

    标签: angular typescript dependency-injection


    【解决方案1】:

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

    { 
        provide: 'TestModelFactory', 
        useFactory: () => {
            return (http, config) => { 
                return new TestModel(http, config);
            };
        },
        deps: [Http, CONFIG];
    }
    

    然后像这样使用它

    @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);
        }
    }
    

    您还可以支持每个实例的参数,例如

    { 
        provide: 'TestModelFactory', 
        useFactory: (json) => {
            return (http, config) => { 
                return new TestModel(http, config, json);
            };
        },
        deps: [Http, CONFIG];
    }
    

    然后像这样使用它

    @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);
        }
    }
    

    但您并不需要使用 DI。您已经将HttpCONFIG 注入到您的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));
    
        }
    }
    

    在每种情况下,您都需要提供一些方法来从 result 初始化 TestModel,例如通过将 JSON 传递给构造函数并从传递的 JSON 初始化 TestModel 的成员。

    另见Angular2: How to use multiple instances of same Service?

    【讨论】:

    • 感谢您的回答。我有一些问题。我假设提供程序块(第一个和第三个代码块)将进入我的应用程序提供程序数组?在提供程序块中,行“new TestModel(http, config), deps: [Http, CONFIG];”我收到错误,我将“deps”属性移到了使用工厂下面,因此它是提供者对象的属性根级别。现在,当我使用代码时,我得到 this.testModelFactory 不是函数
    • 代码应该添加到提供者数组bootstrap(AppComponent, [OtherProvider, {provide: ...}])@Component(... providers: [{provide: ...}])中。 deps 只是一个示例依赖项,您可以根据实际需求删除或替换它(需要传递给服务构造函数的内容9
    • 搞定了!我必须进行一些更改,在提供程序对象中,deps 必须位于对象的根级别,而不是内部函数内部。两个函数也需要返回。所以外层函数,useFactory函数需要返回内层函数,内层函数需要返回新模型。
    • 抱歉,你是对的,我错过了(目前也只在电话上)很高兴听到你想通了。
    • 我将您的代码编辑为对我有用的代码。再次感谢您的帮助!
    【解决方案2】:

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

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

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

    【讨论】:

      【解决方案3】:

      感谢以上各位, 这是我使用的一个工作 plunker。希望对你有帮助

      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 {}
      

      【讨论】:

      • 您应该将此作为对帖子的编辑或对您选择的答案的评论,以便发现此问题的人可以看到它。
      猜你喜欢
      • 1970-01-01
      • 2021-06-13
      • 2013-11-26
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多