如何在Angular2中基于特定的构建环境注入不同的服务?

new*_*bie 40 angular

我有HeroMockService这将返回模拟数据HeroService,并将回调终端服务从数据库中检索英雄.

假设Angular2已建立的环境,我打算注入HeroMockServiceAppComponent如果当前构建环境"dev-mock".如果是当前构建环境"dev-rest",则HeroService应该注入AppComponent.

我想知道如何实现这一目标?

Pau*_*tha 24

IMO,更好的选择是使用angular-in-memory-web-api.它嘲笑使用的后端Http,因此它不是进行实际的XHR调用,而是抓取您提供给它的数据.要获得它,只需安装

npm install --save angular-in-memory-web-api
Run Code Online (Sandbox Code Playgroud)

要创建数据库,请createDb在您的实现中实现该方法InMemoryDbService

import { InMemoryDbService } from 'angular-in-memory-web-api'

export class MockData implements InMemoryDbService {
  let cats = [
    { id: 1, name: 'Fluffy' },
    { id: 2, name: 'Snowball' },
    { id: 3, name: 'Heithcliff' },
  ];
  let dogs = [
    { id: 1, name: 'Clifford' },
    { id: 2, name: 'Beethoven' },
    { id: 3, name: 'Scooby' },
  ];
  return { cats, dogs, birds };
}
Run Code Online (Sandbox Code Playgroud)

然后配置它

import { InMemoryWebApiModule } from 'angular-in-memory-web-api';

@NgModule({
  imports: [
    HttpModule,
    InMemoryWebApiModule.forRoot(MockData, {
      passThruUnknownUrl: true
    }),
  ]
})
Run Code Online (Sandbox Code Playgroud)

现在,当您使用Http并向其发出请求时/api/cats,将从数据库中获取所有猫.如果你去/api/cats/1它会得到第一只猫.您可以执行所有CRUD操作,GET,POST,PUT,DELETE.

需要注意的一点是,它需要一个基本路径.在示例中/api是基本路径.您还可以在配置中配置根(与基础不同)路径

InMemoryWebApiModule.forRoot(MockData, {
  rootPath: 'root',
  passThruUnknownUrl: true // forwards request not in the db
})
Run Code Online (Sandbox Code Playgroud)

现在你可以使用了/root/api/cats.


UPDATE

关于如何从开关切换到生产的问题,您可以使用工厂来创建提供者.如果您要使用模拟服务而不是内存中的web-api,情况也是如此

providers: [
  Any,
  Dependencies
  {
    // Just inject `HeroService` everywhere, and depending
    // on the environment, the correct on will be chosen
    provide: HeroService, 
    useFactory: (any: Any, dependencies: Dependencies) => {
      if (environment.production) {
        return new HeroService(any, dependencies);
      } else {
        return new MockHeroService(any, dependencies);
      }
    },
    deps: [ Any, Dependencies ]
]
Run Code Online (Sandbox Code Playgroud)

至于in-memory-web-api,我需要回复你(我需要测试一个理论).我刚开始使用它,并没有达到我需要切换到生产的程度.现在我只有上面的配置.但我确信有一种方法可以让它工作而不必改变任何东西

更新2

好吧,对于im-memory-web-api我们可以做的而不是导入模块,就是提供XHRBackend模块提供的功能.这XHRBackendHttp用于进行XHR调用的服务.内存中的wep-api嘲笑这项服务.这就是所有模块所做的.所以我们可以使用工厂自己提供服务

@NgModule({
  imports: [ HttpModule ],
  providers: [
    {
      provide: XHRBackend,
      useFactory: (injector: Injector, browser: BrowserXhr,
                   xsrf: XSRFStrategy, options: ResponseOptions): any => {
        if (environment.production) {
          return new XHRBackend(browser, options, xsrf);
        } else {
          return new InMemoryBackendService(injector, new MockData(), {
            // This is the configuration options
          });
        }
      },
      deps: [ Injector, BrowserXhr, XSRFStrategy, ResponseOptions ]
    }
  ]
})
export class AppHttpModule {
}
Run Code Online (Sandbox Code Playgroud)

请注意BrowserXhr,XSRFStrategyResponseOptions依赖.这就是原创XHRBackend的创作方式.现在,不要导入HttpModule到您的应用模块,只需导入AppHttpModule.

至于那个environment,你需要弄清楚的.使用angular-cli,当我们构建生产模式时,已经是一个自动切换到生产的环境.

这是我用来测试的完整示例

import { NgModule, Injector } from '@angular/core';
import { HttpModule, XHRBackend, BrowserXhr,
         ResponseOptions,  XSRFStrategy } from '@angular/http';

import { InMemoryBackendService, InMemoryDbService } from 'angular-in-memory-web-api';

let environment = {
  production: true
};

export class MockData implements InMemoryDbService {
  createDb() {
    let cats = [
      { id: 1, name: 'Fluffy' }
    ];
    return { cats };
  }
}

@NgModule({
  imports: [ HttpModule ],
  providers: [
    {
      provide: XHRBackend,
      useFactory: (injector: Injector, browser: BrowserXhr,
                   xsrf: XSRFStrategy, options: ResponseOptions): any => {
        if (environment.production) {
          return new XHRBackend(browser, options, xsrf);
        } else {
          return new InMemoryBackendService(injector, new MockData(), {});
        }
      },
      deps: [ Injector, BrowserXhr, XSRFStrategy, ResponseOptions ]
    }
  ]
})
export class AppHttpModule {
}
Run Code Online (Sandbox Code Playgroud)


voi*_*oid 24

见下文我的解决方案基于@peeskillet之一.

为您的示例创建一个模拟实际服务实现的接口IHeroService.

export interface IHeroService {
    getHeroes();
}

export class HeroService implements IHeroService {
    getHeroes() {
    //Implementation goes here
    }
}

export class HeroMockService implements IHeroService {
    getHeroes() {
    //Mock implementation goes here
}
Run Code Online (Sandbox Code Playgroud)

假设您已使用Angular-CLI创建了Angular应用程序,请在您的environment.ts文件中添加适当的实现,例如:

import { HeroService } from '../app/hero.service';

export const environment = {
  production: false,
  heroService: HeroService
};
Run Code Online (Sandbox Code Playgroud)

对于每个不同的,environment.(prod|whatever).ts您必须定义heroService指向实现并添加导入.

现在,假设您要在AppModule类中导入服务(您可以在需要该服务的组件上执行此操作)

@NgModule({
  declarations: [
    AppComponent,
  ],
  imports: [
    FormsModule,
    HttpModule,
    AlertModule.forRoot()
  ],
  providers: [
    {
      provide: 'IHeroService',
      useClass: environment.heroService
    }
  ],
  bootstrap: [AppComponent]
})
export class AppModule { }
Run Code Online (Sandbox Code Playgroud)

重要的部分是提供者:

  providers: [
    {
      provide: 'IHeroService',
      useClass: environment.heroService
    }
Run Code Online (Sandbox Code Playgroud)

现在,无论您想在何处使用该服务,都必须执行以下操作:

import { IHeroService } from './../hero.service';

export class HeroComponent {

constructor(@Inject('IHeroService') private heroService: IHeroService) {}
Run Code Online (Sandbox Code Playgroud)

来源: 是否可以使用angular2注入接口? https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be http://tattoocoder.com/angular-cli-using-the-environment-option/

  • 恕我直言,这应该是选定的答案!它满足几种清晰的代码模式,如单一责任模式,开放封闭原则和控制模式的反转,而不是其他任何解决方案. (4认同)
  • 听起来很棒,但我得到:`警告检测到循环依赖:my.service.ts - > logger.service.ts - > src/environments/environment.ts - > my.service.ts` (2认同)

joc*_*cki 7

如果将代码组织到Angular2模块中,则可以创建一个附加模块来导入模拟服务,例如:

@NgModule({
   providers: [
      { provide: HeroService, useClass: HeroMockService }
   ]
})
export class MockModule {}
Run Code Online (Sandbox Code Playgroud)

假设您为核心模块中的普通服务声明导入:

@NgModule({
   providers: [ HeroService ]
})
export class CoreModule {}
Run Code Online (Sandbox Code Playgroud)

只要你导入MockModule之后CoreModule,那么为HeroServicetoken 注入的值就是HeroMockService.这是因为如果同一令牌有两个提供程序,Angular将使用最新值.

然后,您可以MockModule根据特定值(表示构建环境)自定义导入,例如:

// Normal imported modules
var importedModules: Array<any> = [
   CoreModule,
   BrowserModule,
   AppRoutingModule
];

if (process.env.ENV === 'mock') {
   console.log('Enabling mocked services.');
   importedModules.push(MockModule);
}

@NgModule({
    imports: importedModules
})
export class AppModule {}
Run Code Online (Sandbox Code Playgroud)


Pac*_*ace 5

这些答案中有许多是正确的,但无法通过摇树来解决。模拟服务将作为最终应用程序的一部分包含在内,而这通常是不需要的。此外,切换模拟模式并不容易。

我创建了一个解决这些问题的工作示例:https : //github.com/westonpace/angular-example-mock-services

  1. 对于每个服务,创建一个抽象类或一个值令牌和接口。创建一个实现抽象类/接口的模拟服务和真实服务。
  2. 创建一个提供所有模拟服务的MockModule和一个提供所有真实服务的RealModule(确保使用useClass/ provide字段提供interface / abstract类)
  3. 在适当的environment.*.ts文件中加载RealModule或MockModule
  4. 更改angular.json用于angular-cli创建新构建目标的mock文件,该目标将使用注入的模拟环境文件进行构建MockModule。创建一个新的serve配置来提供模拟构建,以便您可以这样做ng serve -c mock。更改默认量角器配置,以便它使用ng e2e模拟的服务目标,以便针对您的模拟服务运行。

  • 到目前为止最好的方法,还有一个完整的例子。这应该是 IMO 公认的答案 (2认同)