¿Cómo inyectar un servicio diferente basado en cierto entorno de construcción en Angular2?


Tengo HeroMockService que devolverá datos burlados y HeroService que llamará al servicio back-end para recuperar héroes de la base de datos.

Asumiendo que Angular2 tiene entorno de compilación, estoy planeando inyectar HeroMockService a AppComponent si el entorno de compilación actual es "dev-mock". Si el entorno de compilación actual es "dev-rest", HeroService debe inyectarse a AppComponent en su lugar.

Me gustaría saber cómo puedo lograr esto?

 26
Author: newbie, 2016-10-09

8 answers

IMO, una mejor opción sería utilizar el angular-in-memory-web-api. Se burla del backend que usa Http, por lo que en lugar de hacer una llamada XHR real, simplemente toma los datos que le proporcionas. Para conseguirlo, solo tiene que instalar

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

Para crear la base de datos se implementa el método createDb en su 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 };
}

Luego configúrelo

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

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

Ahora cuando uses Http y hagas una petición a /api/cats obtendrá todos los gatos de la base de datos. Si vas a /api/cats/1 tendrá el primer gato. Puedes hacer todas las operaciones CRUD, GET, POST, PUT, DELETE.

Una cosa a tener en cuenta es que espera una ruta base. En el ejemplo /api está la ruta base. También puede configurar una ruta raíz (esto es diferente de la base), en la configuración

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

Ahora puedes usar /root/api/cats.


ACTUALIZAR

Con respecto a la pregunta sobre cómo cambiar de desarrollo a producción, puede usar una fábrica para crear los proveedores. Lo mismo sería cierto si debía usar su servicio simulado en lugar de la 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 ]
]

En cuanto a la in-memory-web-api, necesito volver a ti (necesito probar una teoría). Acabo de empezar a usarlo, y no he llegado al punto en el que necesito cambiar a la producción. Ahora mismo solo tengo la configuración anterior. Pero estoy seguro de que hay una manera de hacer que funcione sin tener que cambiar nada

ACTUALIZACIÓN 2

Ok así que para la im-memory-web-api lo que podemos hacer en lugar de importar el Module, es simplemente proporcionar el XHRBackend que el módulo proporciona. El XHRBackend es el servicio que Http utiliza para hacer llamadas XHR. La in-memory-wep-api se burla de ese servicio. Eso es todo lo que hace el módulo. Así que solo podemos proporcionar el servicio nosotros mismos, utilizando una fábrica

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

Observe la BrowserXhr, XSRFStrategy, y ResponseOptions dependencias. Así es como se crea el XHRBackend original. Ahora, en lugar de importar el HttpModule en su módulo de aplicación, solo importa el AppHttpModule.

En cuanto al environment, eso es algo que necesitas averiguar. Con angular-cli, el ya es un entorno que se cambia automáticamente a producción cuando construimos en modo de producción.

Aquí está el ejemplo completo que usé para probar con

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 {
}
 21
Author: Paul Samsotha,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-20 12:08:36

Ver a continuación mi solución se basa en @peeskillet one.

Cree una interfaz que el servicio mocky real implementen, para su ejemplo IHeroService.

export interface IHeroService {
    getHeroes();
}

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

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

Suponiendo que haya creado la aplicación Angular utilizando Angular-CLI, en su archivo environment.ts agregue la implementación adecuada, por ejemplo:

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

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

Para cada environment.(prod|whatever).ts diferente, debe definir un heroService que apunte a la implementación y agregar la importación.

Ahora, supongamos que desea importar el servicio en la clase AppModule (puede hacerlo en el componente donde también necesita el servicio)

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

La parte importante es el proveedor:

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

Ahora donde quieras usar el servicio tienes que hacer lo siguiente:

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

export class HeroComponent {

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

Fuentes: ¿Es posible inyectar interfaz con angular2? https://medium.com/beautiful-angular/angular-2-and-environment-variables-59c57ba643be http://tattoocoder.com/angular-cli-using-the-environment-option /

 16
Author: void,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-06-06 13:18:20

Si organiza su código en módulos Angular2, puede crear un módulo adicional para importar servicios burlados, por ejemplo:

@NgModule({
   providers: [
      { provide: HeroService, useClass: HeroMockService }
   ]
})
export class MockModule {}

Suponiendo que declare importación para servicios normales en su módulo principal:

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

Siempre que importe MockModule después de CoreModule, entonces el valor que se inyectará para HeroService token es HeroMockService. Esto se debe a que Angular usará el último valor si hay dos proveedores para el mismo token.

Luego puede personalizar la importación para MockModule basado en cierto valor (que representa el entorno de compilación), por ejemplo:

// 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 {}
 3
Author: jocki,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2016-10-18 16:22:54

Muchas de estas respuestas son correctas pero fallarán sacudiendo árboles. Los servicios simulados se incluirán como parte de la aplicación final que generalmente no es lo que se desea. Además, no es fácil entrar y salir del modo de simulación.

He creado un ejemplo de trabajo que resuelve estos problemas: https://github.com/westonpace/angular-example-mock-services

  1. Para cada servicio, cree una clase abstracta o una interfaz de token de valor. Crear un simulacro servicio y un servicio real que implementa la clase abstracta / interfaz.
  2. Cree un MockModule que proporcione todos sus servicios simulados y un RealModule que proporcione todos sus servicios reales (asegúrese de usar el useClass/provide campos para proporcionar la clase interfaz / abstract)
  3. En los archivos environment.*.ts apropiados se carga el RealModule o el MockModule
  4. Hacer cambios en el archivo angular.json utilizado por angular-cli para crear un nuevo destino de compilación mock que se compila con el mock archivo de entorno que inyecta el MockModule. Cree una nueva configuración serve que sirva la compilación simulada para que pueda hacer ng serve -c mock. Cambie la configuración predeterminada del transportador de modo que utilice el destino de servicio simulado para que ng e2e se ejecute contra sus servicios simulados.
 1
Author: Pace,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-08-09 17:09:09

Para aquellos como yo que obtienen el siguiente error:

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

Para mi propósito era para un ErrorHandler:

{
    provide: ErrorHandler,
    useFactory: getErrorHandler,
    deps: [ Injector ]
}
...
export function getErrorHandler(injector: Injector): ErrorHandler {
    if (environment.production) {
        return new MyErrorHandler(injector);
    } else {
        return new ErrorHandler();
    }
}
 0
Author: RVandersteen,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-05-08 18:02:37

Una solución grande y simple se proporciona en este post por Luuk G.

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

En resumen:

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 { }
 0
Author: Dmitri R117,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-10-04 22:16:05

Esto funcionó para mí en Angular 6. Aprovecha el enfoque de interfaz de@void (donde tanto el servicio verdadero como el servicio simulado implementan la interfaz de servicio). La respuesta aceptada omite mencionar que el propósito de una fábrica es devolver una implementación de una interfaz.

Heroservice/heroservice.módulo.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.módulo.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
    ),
  ],

// ...

Algunos/algunos.componente.ts

Importar y utilizar la interfaz como lo haría el servicio real (la implementación se "proporcionará" en tiempo de ejecución).

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

// ...
 0
Author: FizxMike,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2018-07-10 13:24:52

@Inject('IHeroService') no va bien con la producción. En mi caso acabo de hacer esto:

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


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

La biblioteca Priority que quería importar no tenía un módulo exportado, así que necesitaba hacer un poco de trampa. Al principio probé la forma @Inject('IHeroService') pero cuando compilé como prod no funcionó.

Espero que ayude a alguien!

 -1
Author: neomib,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-08-02 13:15:13