Swap Mock and Remote Providers - best practice? (rc0)


#1

Hi there,

I’m wondering how to solidly build my app to be able to swap providers. E.g. for test and and rapid development, I want to use mock providers, while on production (and sometimes on development) I want to use remote rest service providers.

Since I have to import the specific classes of the providers in the ts files where they are used, I’m wondering how I can just swap them without adjust each single file?

Ideally, I’d like to have an interface, let the using classes just import the interface and let DI injection do the rest (isn’t this the main goal of DI?). Somewhere at app.module.ts or so, I’d like to have some switch which chooses to inject mock providers or real providers.

However, I was not able to get this working yet. Any ideas? How do you do this?

Greetings,
Rasioc


#2

nobody with an idea?


#3

I’ve recently run into the same issue, and found it pretty tricky to find any examples. There are a few discussions over on StackOverflow though, such as this one for angular 2 and this one in general for typescript.

The main points are:

  • Interface implementation information is lost when .ts gets transpiled to .js; which means you can’t easily declare an interface, and have two implementations, and list the implementation in your ionic app’s providers array.
  • You’ll likely have to use a string to represent the resource being injected, as opposed to the interface / class type. This means the consumer side of your provider will look like this:
constructor(@Inject('SearchProvider') public searchProvider:SearchProvider) {}

(notice that the Inject attribute is now a string, rather than a type)

And in your app.module.ts, you specify which provider to use like this:

providers: [
{ provide: 'SearchProvider', useClass: MockSearchProvider },

//{ provide: 'SearchProvider', useClass: SearchProvider }, // Commented out real provider
...
];

This seems to work as expected for the current version of Ionic 2. Your decision to inject mock or real versions of providers is now at least centralised to the app.module file.

How exactly you set up the interface / abstract class for the provider probably depends on your preferences. You could have an actual ts interface there, with two disparate implementations. For development builds at least, you can even just reference the real provider in the consumer, but have the application inject the mock for you, simply overriding it at runtime (not sure if this approach would cause any issues on a full Android build, etc).

Hope this helps.


#4

Thanks for your response!

(edit: my previous post was quite wrong due to a misunderstanding I just noticed)
Your solution seems to be the best option right now.


#5

I have been trying to do something similar, however, my implementation attempted to expand on the way providers were declared in a seperate providers.ts file in the new Ionic Super Starter: https://github.com/driftyco/ionic-starter-super/blob/master/src/providers/providers.ts

In my method, I tried to declare the references here once, commenting out the mock/live providers that I didn’t want to use.

e.g. providers.service.ts

import { EventService } from './event.service';
import { SettingsService } from './settings.service';

//Production
import { APIService } from './api/cms.service';
import { ProductService } from './api/product.service';
import { CartService } from './api/cart.service';

//Mocks
import { MockAPIService } from './mock/mock.service';
//import { ProductService } from './mock/product.service';
//import { CartService } from './mock/cart.service';

export {
  APIService,
  EventService,
  ProductService,
  CartService,
  MockAPIService,
};

I then made reference to these services by always pointing to the providers.ts anywhere that they where needed e.g.

//Providers
import {APIService} from '../providers/providers.service';
import {MockService} from '../providers/providers.service';
import {EventService} from '../providers/event.service';
import {ProductService} from '../providers/providers.service';
import {CartService} from '../providers/providers.service';

This worked for the most part, but just today I have run into an issue, where if one service refers to another that was also defined in providers.service.ts, the injection doesn’t seem to work properly. No TS issues, but on app run I get unresolved dependencies/injection in my JS console. For example:

Can't resolve all parameters for CartService: (Storage, Http, ?, EventService).

CartServices uses ProductService from the same providers.service file, uses it in the constructor as follows:

export class CartService {

  constructor(public storage: Storage, public http: Http, public productService: ProductService, public event: EventService) {
  }

Would really appreciate any explanation as to why this is the case, or how I could address it?