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

时间:2016-10-09 09:40:01

标签: angular

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

假设Angular2有构建环境,我打算在当前构建环境为HeroMockService的情况下向AppComponent注入"dev-mock"。如果当前构建环境为"dev-rest",则应将HeroService注入AppComponent

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

9 个答案:

答案 0 :(得分:23)

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

npm install --save angular-in-memory-web-api

要创建数据库,请在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 };
}

然后配置它

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

@NgModule({
  imports: [
    HttpModule,
    InMemoryWebApiModule.forRoot(MockData, {
      passThruUnknownUrl: true
    }),
  ]
})

现在,当您使用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
})

现在您可以使用/root/api/cats

更新

关于如何从开关切换到生产的问题,您可以使用工厂来创建提供者。如果您使用模拟服务而不是in-memory-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 ]
]

就内存网络api而言,我需要回复你(我需要测试一个理论)。我刚刚开始使用它,并且还没有达到我需要切换到生产的程度。现在我只有上面的配置。但我确信有一种方法可以让它无需改变任何东西

更新2

好的,对于im-memory-web-api,我们可以做的不是导入模块,而是提供模块提供的XHRBackendXHRBackendHttp用于进行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 {
}

请注意BrowserXhrXSRFStrategyResponseOptions依赖项。这就是原始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 {
}

答案 1 :(得分:21)

见下文我的解决方案基于@peeskillet one。

为您的示例IHeroService创建一个 mock real 服务实现的接口。

export interface IHeroService {
    getHeroes();
}

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

export class HeroMockService implements IHeroService {
    getHeroes() {
    //Mock implementation goes here
}

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

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

export const environment = {
  production: false,
  heroService: HeroService
};

对于每个不同的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 { }

重要的部分是提供者:

  providers: [
    {
      provide: 'IHeroService',
      useClass: environment.heroService
    }

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

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

export class HeroComponent {

constructor(@Inject('IHeroService') private heroService: IHeroService) {}

来源: Is it possible to inject interface with angular2? https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be http://tattoocoder.com/angular-cli-using-the-environment-option/

答案 2 :(得分:5)

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

@NgModule({
   providers: [ HeroService ]
})
export class CoreModule {}

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

MockModule

只要您在CoreModule之后导入HeroService,就会为HeroMockService令牌注入的值为MockModule。这是因为如果同一令牌有两个提供者,Angular将使用最新值。

然后,您可以根据特定值(代表构建环境)自定义// 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 {} 的导入,例如:

{{1}}

答案 3 :(得分:5)

许多这样的答案是正确的,但是将使摇树失败。模拟服务将作为最终应用程序的一部分包含在内,而这通常是不需要的。此外,切换模拟模式并不容易。

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

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

答案 4 :(得分:0)

对于像我这样的人会收到以下错误:

ERROR in Error encountered resolving symbol values statically.
Function calls are not supported. Consider replacing the function
or lambda with a reference to an exported function

出于我的目的,它适用于ErrorHandler:

{
    provide: ErrorHandler,
    useFactory: getErrorHandler,
    deps: [ Injector ]
}
...
export function getErrorHandler(injector: Injector): ErrorHandler {
    if (environment.production) {
        return new MyErrorHandler(injector);
    } else {
        return new ErrorHandler();
    }
}

答案 5 :(得分:0)

Luuk G在这篇文章中提供了一个非常简单的解决方案。

https://hackernoon.com/conditional-module-imports-in-angular-518294aa4cc

总结:

let dev = [
StoreDevtoolsModule.instrument({
    maxAge: 10,
  }),
];

// if production clear dev imports and set to prod mode
if (process.env.NODE_ENV === 'production') {
  dev = [];
  enableProdMode();
}

@NgModule({
  bootstrap: [
    Base,
  ],
  declarations: [
    Base,
  ],
  imports: [
    Home,
    RouterModule.forRoot(routes, { useHash: true }),
    StoreModule.forRoot(reducers.reducerToken),
    ...dev,
  ],
  providers: [
  ],
})
export class Root { }

答案 6 :(得分:0)

这在Angular 6中对我有用。利用@void's Interface approach(其中真正的服务和模拟服务都实现服务接口)。接受的答案忽略了提及工厂的目的是返回接口的实现。

heroservice / heroservice.module.ts

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IHeroService } from './ihero.service';
import { HeroService } from './hero.service';
import { MockHeroService } from './mock-hero.service';


@NgModule({
  imports: [
    CommonModule
  ],
})
export class HeroServiceModule {
  static forRoot(mock: boolean): ModuleWithProviders {
    return {
      ngModule: HeroServiceModule ,
      providers: [
        {
          provide: IHeroService,
          useClass: mock ? MockHeroService : HeroService,
        },
      ],
    };
  }
}

app.module.ts

import { NgModule, isDevMode } from '@angular/core';
import { HeroServiceModule } from './heroservice/heroservice.module';

// ...

  imports: [
    BrowserModule,
    HttpClientModule,
    // ...
    HeroServiceModule.forRoot(
      isDevMode() // use mock service in dev mode
    ),
  ],

// ...

some / some.component.ts

导入和使用界面的方式与实际服务相同(实现将在运行时“提供”。)

import { IHeroService } from '../heroservice/iheroservice.service';

// ...

答案 7 :(得分:0)

这是使用条件环境逻辑执行此操作的最新方法的示例。请注意,EventService和LogService只是您可能拥有的其他服务的示例。

providers: [
    EventService,
    LogService,
    MyService,
    {
      provide: MyService,
      useClass: (environment.custom == null || environment.custom === '' || environment.custom === 'MyCustomerService')
        ? MyCustomService : MyService
    }
  ]
]

答案 8 :(得分:-1)

@Inject(&#39; IHeroService&#39;)在生产方面做得不好。就我而言,我只是这样做了:

import { Injectable } from '@angular/core';
import * as Priority from 'priority-web-sdk';


@Injectable()
export class PriorityService
{
    priority;
    constructor( ){
        this.priority=Priority;
    }
}

我要导入的Priority库没有导出的模块,所以我需要作弊。 起初我尝试@Inject('IHeroService')方式但是当我编译为prod时它没有工作。

希望它有所帮助!

相关问题