如何在Angular2中正确使用依赖注入?

时间:2016-01-22 09:48:41

标签: dependency-injection angular

我在解决DI问题方面取得了成功。我已经阅读了一些教程并获得了jist但是在使用我的自定义服务进行了一些嵌套的DI后,事情开始崩溃了。

有人可以解释何时使用useFactory而不是useClass?我已经看过ng2文档,我已经看过这些例子,但是我无法将它们映射到我的问题中。目前我的引导程序看起来像这样:

bootstrap(
    App,
    [
        FORM_PROVIDERS,
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(LocationStrategy, { useClass: PathLocationStrategy }),
        provide(RequestOptions, { useClass: DefaultRequestOptions }),
        provide(MsgService, { useClass: MsgService }),
        provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
        provide(AuthService, { useFactory: (HttpAdvanced) => new AuthService(HttpAdvanced), deps: [HttpAdvanced, HTTP_PROVIDERS, MsgService] }),
        provide(FormBuilderAdvanced, { useFactory: (FormBuilder, HttpAdvanced) => new FormBuilderAdvanced(FormBuilder, HttpAdvanced), deps: [FormBuilder, HttpAdvanced] }),
        provide(MsgServiceInternal, { useClass: MsgServiceInternal })
    ]
);

我的最新一期是:

EXCEPTION: Error during instantiation of AuthService! (HeaderBar -> AuthService).
ORIGINAL EXCEPTION: TypeError: this.http.get is not a function

我的依赖项就像

一样
HttpAdvanced        -> Http(ng2), MsgService
MsgService          -> MsgServiceInternal
AuthService         -> HttpAdvanced
FormBuilderAdvanced -> FormBuilder(ng2), HttpAdvanced

1。 我是否正确使用了provide / useClass / useFactory,如何提供具有其他依赖关系的服务?

另外,我的代码中有一个位置:

static isUserInjector() {
    return (next, prev) => Injector.resolveAndCreate([AuthService, provide(HttpAdvanced, { useClass: HttpAdvanced })]).get(AuthService).isUser();
}

因为我希望有一个我提供给

的功能
@CanActivate(AuthService.isEditorInjector())

但我无法使用构造函数注入,因为@CanActivate超出了类范围,因此我无法在控制器中注入服务,然后像@CanActivate(this.authService.isEditor())

那样引用

2. 对此有什么好的解决方案?

一些代码:

@Component({
    selector: 'ShowStats',
    templateUrl: './dest/views/showStats/showStats.html',
    directives: [ COMMON_DIRECTIVES, UsersCount, AdminsList, GlobalWishlist, PopularTrack ]
})
export class ShowStats {
    authService : AuthService;

    constructor( authService : AuthService ){
        this.authService = authService;
    }
}

... next file ...

@Injectable()
export class HttpAdvanced {
    msgService: MsgService;
    http: Http;

    constructor(msgService: MsgService, http: Http) {
        this.msgService = msgService;
        this.http = http;
    }

    /*
     * This is for plain ol' GET requests .. with callback of course.
     */
    public get(url, callback) {
        return this.http.get(url).subscribe((res) => {
            let data = res.json().data;
            callback(data);
        }, this.msgService.httpErrorHandler);
    }
.... other code for HttpAdvanced

3 导入文件的顺序对DI很重要吗?我想我注意到,因为我在同一个文件中有MsgService和MsgServiceInternal,并且MsgService依赖于Internal,我之前必须放置Internal但是我不是100%它是否与importing的顺序相同? / p>

4 所以,如果我只是这样做:

bootstrap(
    App,
    [
        FORM_PROVIDERS,
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(LocationStrategy, { useClass: PathLocationStrategy }),
        provide(RequestOptions, { useClass: DefaultRequestOptions }),
        MsgService,
        HttpAdvanced,
        AuthService,
        FormBuilderAdvanced,
        MsgServiceInternal
    ]
);

我明白了:

Cannot resolve all parameters for 'FormBuilderAdvanced'(?, ?). 
Make sure that all the parameters are decorated with Inject or have 
valid type annotations and that 'FormBuilderAdvanced' is decorated 
with Injectable.

我曾经使用useFactory删除此错误,但我现在感到困惑。这是否意味着没有注入deps,因为它无法看到它们或者是什么?

Form类:

export class FormBuilderAdvanced {
    http: HttpAdvanced;
    fb: FormBuilder;

    constructor(fb: FormBuilder, http: HttpAdvanced) {
        this.fb = fb;
        this.http = http;
    }

    create(controlNames: string[], submissionUrl: string, getter?: any) {
        return new Form(this.fb, this.http, controlNames, submissionUrl, getter);
    }
}

2 个答案:

答案 0 :(得分:6)

你的问题并没有提供足够的信息来确定,但这可能就足够了

bootstrap(
    App,
    [
        FORM_PROVIDERS,
        ROUTER_PROVIDERS,
        HTTP_PROVIDERS,
        provide(LocationStrategy, { useClass: PathLocationStrategy }),
        provide(RequestOptions, { useClass: DefaultRequestOptions }),
        // provide(MsgService, { useClass: MsgService }),
        MsgService, // is just fine when no special behavior is required
        // provide(HttpAdvanced, { useFactory: (MsgService, HTTP_PROVIDERS) => new HttpAdvanced(MsgService, HTTP_PROVIDERS), deps: [MsgService, HTTP_PROVIDERS] }),
        provide(Http, {useClass: HttpAdvanced});
        AuthService,
        provide(FormBuilder, { useClass: FormBuilderAdvanced}),
        MsgServiceInternal)
    ]
);

如果要使类可用于注入,只需将类型添加到提供者列表(provide(AuthService),只需AuthService执行相同操作)。 如果您想要注入与请求的类不同的类,请使用useClass

示例

@Injectable()
export class MyService {
  constructor(private http: Http) {
  }
}

如果您的提供商包含

provide(Http, {useClass: HttpAdvanced});

然后MyService(依赖于Http)会被注入HttpAdvanced。 确保在HTTP_PROVIDERS之后使用此行覆盖Http中包含的默认HTTP_PROVIDERS提供商。

如果DI无法自行解决依赖关系,因为它们不仅仅是其他提供商,那么请使用useFactory

如上所述,

  

确保在HTTP_PROVIDERS之后使用此行覆盖Http中包含的默认HTTP_PROVIDERS提供商。

提供商列表中的顺序很重要。 当提供者列表包含一个类型的多个提供者时,则只使用最后一个提供者。

导入的顺序并不重要。

一个文件中依赖类的顺序很重要,因为没有提升类。

实施例

export class ClassA {
   // constructor(private b: ClassB) {} // ClassB is unknown here
   constructor(@Inject(forwardRef(() => DataService)) {
   }
}

export class ClassB {
}

如果ClassB的代码高于ClassA,则forwardRef()是不必要的。

另见Class is not injectable if it is defined right after a component with meta annotation

答案 1 :(得分:4)

为了完成Günter的明确答案,我会说在以下用例中我会使用useFactory自己实例化与提供者相关的类:

  • 如果要选择要注入与提供者关联的元素的构造函数的元素。例如,如果要扩展HTTP类并明确提供参数的特定具体类(此处为XHRBackend而不是ConnectionBackend)。这是一个示例:

    bootstrap(AppComponent, [HTTP_PROVIDERS, ROUTER_PROVIDERS,
      new Provider(Http, {
        useFactory: (backend: XHRBackend, defaultOptions: RequestOptions) => new CustomHttp(backend, defaultOptions),
        deps: [XHRBackend, RequestOptions]
      })
    ]);
    

@Langley在这个问题中提供了有趣的提示:Handling 401s globally with Angular

  • useFactory的另一个很酷的用法是将一个类从第三方库实例化到Angular2(Zones)的上下文中

    bootstrap(App, [
      provide(Mousetrap, { useFactory: () => new Mousetrap() })
    ]);
    

@alexpods在这个问题中提供了这个非常优雅的解决方案:View is not updated on change in Angular2

希望它可以帮到你, 亨利