ionic-team / capacitor

Build cross-platform Native Progressive Web Apps for iOS, Android, and the Web ⚡️
https://capacitorjs.com
MIT License
12.35k stars 1.01k forks source link

feat: Plugins wrapper for Angular #2368

Closed MatthieuNarcisi closed 4 years ago

MatthieuNarcisi commented 4 years ago

Feature Request

Describe the Feature Request

Services wrapper for Plugins on Angular (or other Frameworks) are not available. Preventing dependency injection and good testing.

Platform Support Requested

Describe Preferred Solution

Libraries for main frameworks (Angular, React, Vue) should be released, providing wrapping service to be able to inject the plugins in other classes to allow for testing and better maintainability (lazy loading, code analysis...)

Related Code

Service Example

Additional Context

I don't know if it is the right repository to post this issue, but I could not think of another one.

jcesarmobile commented 4 years ago

Issues tagged with feature request are closed but tracked for reactions to gauge interest.

My opinion: Since Capacitor is framework agnostic, I think this should be a separate project/repo as it was done with capacitor react hooks https://github.com/ionic-team/ionic-react-hooks

MatthieuNarcisi commented 4 years ago

I agree with you, it should be a separate project but I did not find a better place to talk about the feature.

I also wanted to make sure that it did not already existed as I took sometime to look for it but could not find anything on the web.

AlanThiec commented 4 years ago

It seems a great idea. I just started a new project and it's boring to wrap every single plugins into an Angular services just in order to test my code.

kensodemann commented 4 years ago

In the interim, I wouldn't go to the extreme of creating a bunch of boring injectable services just in order to test your code.

You can just replace the items on Plugins as such: https://github.com/ionic-team/iv-training-starter/blob/master/src/app/app.component.spec.ts#L11-L25

Use them in your test the usual way: https://github.com/ionic-team/iv-training-starter/blob/master/src/app/app.component.spec.ts#L34-L38

And just keep your code using the objects on Plugins the way that it is: https://github.com/ionic-team/iv-training-starter/blob/master/src/app/app.component.ts#L17-L24

No services needed.

Totally agree that it would be nice to have them, though. It would make the testing more "natural", but it certainly isn't a requirement in many cases.

AlanThiec commented 4 years ago

@kensodemann Thanks, I don't know why I thought they were const.

But finally, I start to love my injectable services (it's maybe my OCD side). I think, that to have my plugins injected in the constructor, is more readable/maintainable to know what a component does.

paolosanchi commented 3 years ago

To better isolate the tests, and test the platform related behaviours, I ended up to inject the Plugins with InjectionToken, because the plugins are interfaces (intances), and not class. So I added the tokens like this:

import { InjectionToken } from "@angular/core";
import { AppPlugin, CameraPlugin, StatusBarPlugin } from "@capacitor/core";
import { Capacitor } from "@capacitor/core/dist/esm/definitions";

export const APP_PLUGIN = new InjectionToken<AppPlugin>('AppPlugin');
export const CAMERA_PLUGIN = new InjectionToken<CameraPlugin>('CameraPlugin');
export const STATUS_BAR_PLUGIN = new InjectionToken<StatusBarPlugin>('StatusBarPlugin');
export const CAPACITOR = new InjectionToken<Capacitor>('Capacitor');

Registered them in the module provider like this:

providers:[
....
{
      provide: CAMERA_PLUGIN,
      useValue: Plugins.Camera,
    },
    {
      provide: APP_PLUGIN,
      useValue: Plugins.App,
    },
    {
      provide: STATUS_BAR_PLUGIN,
      useValue: Plugins.StatusBar,
    },
    {
      provide: CAPACITOR,
      useValue: Capacitor,
    }
]

And finally injected in the component / service constructor like this:

  constructor(
    @Inject(APP_PLUGIN) private app: AppPlugin,
    @Inject(STATUS_BAR_PLUGIN) private statusBar: StatusBarPlugin,
    @Inject(CAPACITOR) private capacitor: Capacitor,
  ) {
}

This way in the tests I can mock directly the injected plugins and do not relay on the dependencies. Be cause I use ng-mocks the result is like this:

  beforeEach(() => {
    // The result of MockBuilder should be returned.
    return MockBuilder(AppComponent, AppModule);
  });

  beforeEach(() =>
    MockInstance(CAPACITOR, () => ({
      isPluginAvailable: () => true
    })),
  );

  // necessary because not available on for the web plugin
  beforeEach(() =>
    MockInstance(STATUS_BAR_PLUGIN, () => ({
      setBackgroundColor: jasmine.createSpy(),
      setStyle: jasmine.createSpy()
    })),
  );

  it('should initialize the app', async () => {

    const fixture = MockRender(AppComponent);
    const component = fixture.point.componentInstance;
    const injector = fixture.point.injector;

    await fixture.whenStable();

    const statusBar = injector.get<StatusBarPlugin>(STATUS_BAR_PLUGIN);

    expect(statusBar.setBackgroundColor).toHaveBeenCalled();
    expect(statusBar.setStyle).toHaveBeenCalled();
    expect(statusBar.setStyle).toHaveBeenCalledWith(jasmine.objectContaining({ style: StatusBarStyle.Dark }));
  });

What do you think? Is there a better way?

ionitron-bot[bot] commented 2 years ago

Thanks for the issue! This issue is being locked to prevent comments that are not relevant to the original issue. If this is still an issue with the latest version of Capacitor, please create a new issue and ensure the template is fully filled out.