How to inject different service based on certain build environment in Angular2?

后端 未结 9 1612
醉话见心
醉话见心 2020-12-04 08:50

I have HeroMockService that will return mocked data and HeroService that will call back end service to retrieve heroes from database.

Assum

相关标签:
9条回答
  • 2020-12-04 09:39

    If you organize your code into Angular2 modules, you can create an additional module to import mocked services, for example:

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

    Assuming that you declare import for normal services in your core module:

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

    As long as you import MockModule after CoreModule, then the value that will be injected for HeroService token is HeroMockService. This is because Angular will use the latest value if there are two providers for the same token.

    You can then customize import for MockModule based on certain value (that represents build environment), for example:

    // 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 {}
    
    0 讨论(0)
  • 2020-12-04 09:43

    IMO, a better option would be to use the angular-in-memory-web-api.

    note: this project was pulled into angular/angular from its old location.

    It mocks the backend that Http uses, so instead of making an actual XHR call, it just grabs the data that you provide to it. To get it, just install

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

    To create the database you implement the createDb method in your 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 };
    }
    

    Then configure it

    import { InMemoryWebApiModule } from 'angular-in-memory-web-api';
    
    @NgModule({
      imports: [
        HttpModule,
        InMemoryWebApiModule.forRoot(MockData, {
          passThruUnknownUrl: true
        }),
      ]
    })
    

    Now when you use Http and make a request to /api/cats it will get all the cats from the db. If you go to /api/cats/1 it will get the first cat. You can do all the CRUD operations, GET, POST, PUT, DELETE.

    One thing to note is that it expects a base path. In the example /api is the base path. You can also configure a root (this is different from base) path, in the configuration

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

    Now you can use /root/api/cats.


    UPDATE

    In regards to the question about how to switch from dev to production, you can use a factory to create the providers. Same would be true if you were to use your mock service instead of the 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 ]
    ]
    

    As far as the in-memory-web-api, I need to get back to you (I need to test a theory). I just started using it, and haven't gotten to the point where I need to switch to production. Right now I just have the above configuration. But I'm sure there's a way to make it work without having to change anything

    UPDATE 2

    Ok so for the im-memory-web-api what we can do instead of importing the Module, is to just provide the XHRBackend that the module provides. The XHRBackend is the service that Http uses to make XHR calls. The in-memory-wep-api mocks that service. That's all the module does. So we can just provide the service ourselves, using a factory

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

    Notice the BrowserXhr, XSRFStrategy, and ResponseOptions dependencies. This is how the original XHRBackend is created. Now instead of importing the HttpModule into your app module, just import the AppHttpModule.

    As far as the environment, that's something you need to figure out. With angular-cli, the is already an environment that gets automatically switched to production when we build in production mode.

    Here's the complete example I used to test with

    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 {
    }
    
    0 讨论(0)
  • 2020-12-04 09:44

    A great and simple solution is provided on this post by Luuk G.

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

    In summary:

    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 讨论(0)
提交回复
热议问题