Creating a own npm module

Product/components used and version/fix level:

I would like to create a service and publish it in a npm library. The Idea is to wrap current exisiting Services and create wrappers for functions and so on. My problem is that i don’t know how to get the library to use the same services as the users c8y client, for auth and so on. Additionally the library currently doesn’t get the services injected. I’m not sure how to solve this issue.

Detailed explanation of the problem:

My current Setup looks like the following:

MyLibModule:

import { NgModule } from '@angular/core';
import { CoreModule } from '@c8y/ngx-components'; // import the c8yclient module
import { MyLibService} from './mylib.service';

@NgModule({
  imports: [CoreModule.forRoot()],
  providers: [MyLibService],
})
export class MyLibModule{}

My Test Application using the Lib Service:

// Assets need to be imported into the module, or they are not available
import { assetPaths } from '../assets/assets';
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { WidgetPluginComponent } from './widget-plugin.component';
import { WidgetPluginConfig } from './widget-plugin-config.component';
import { FormsModule, hookComponent, gettext } from '@c8y/ngx-components';
import { MyLibModule} from 'myc8yWrapperLib';
import { CoreModule } from '@c8y/ngx-components';

@NgModule({
  declarations: [WidgetPluginComponent, WidgetPluginConfig],
  entryComponents: [WidgetPluginComponent, WidgetPluginConfig],
  imports: [CommonModule, FormsModule, CoreModule, MyLibModule],
  exports: [],
  providers: [
    hookComponent({
      id: 'angular.widget.plugin',
      label: gettext('Test Widget for Lib'),
      description: gettext('Widget added from test Widget for Lib module.'),
      component: WidgetPluginComponent,
      previewImage: assetPaths.previewImage,
      configComponent: WidgetPluginConfig,
    }),
  ],
})
export class TestWidgetForLibModule {}

Component using the service:

import { Component, Input } from '@angular/core';
import { MyLibService} from 'myc8yWrapperLib';

@Component({
  selector: 'c8y-widget-plugin',
  template: `
    <div class="p-16">
      <h1>Widget-plugin</h1>
      <p class="text">{{ config?.text || 'No text' }}</p>
      <small>My context is: {{ config?.device?.name || 'No context' }}</small>
    </div>
  `,
  styleUrls: ['./widget-plugin.component.css'],
})
export class WidgetPluginComponent {
  @Input() config;
  constructor(private myService: MyLibService) {
    console.log(myService);
  }
}

Error messages / full error message screenshot / log file:

The Widget compiles and i can access it in the cockpit but the services that my own service relies on don’t get injected.

NullInjectorError: R3InjectorError(TestWidgetForLibModule)[MyLibService-> n -> n -> n -> n]: 
  NullInjectorError: No provider for n!

Can this be done somehow? I could possibly leave out the Module and provide the services in the component my service is used but this is tedious and redundant.

Question related to a free trial, or to a production (customer) instance?

Production

Hi @jakob.hock,

You should not be calling CoreModule.forRoot() in two locations. Since you are now calling CoreModule.forRoot() as part of your library and as part of your shell application, this will cause issues.

For further debugging it would be helpful to know what you are injecting in your MyLibService.
Also consider annotating your service with @Injectable({ providedIn: 'root' }) to get rid of the additional module/provide.

Hello @Tristan_Bastian,

The service is already has @Injectable({ providedIn: 'root' }). When i used this without the module the services to be injected were also not found, so i thought the module was necessary.
The service costructor looks like this:

@Injectable({
  providedIn: 'root',
})
export class MyLibService{
  constructor(
    private invService: InventoryService,
    private invRealtime: ManagedObjectRealtimeService,
    private measurementsService: MeasurementService,
    private measurementRealtimeService: MeasurementRealtimeService,
    private eventService: EventService,
    private eventRealtimeService: EventRealtimeService,
    private fetchClient: FetchClient,
  ) {}

   ...
}

Hi Jakob,

By running your shell application and the plugin in a development build, we could potentially narrow down which of these services it fails to inject.
My guess would be that at least one of the ManagedObjectRealtimeService, MeasurementRealtimeService, EventRealtimeService Services is not provided, where do you provide them?

Regards,
Tristan

I originally tried to provied every service i need in the module to make it easier for the users of the library.

Removing the CoreModule.forRoot() and providing every RealtimeService in the Module fixed the Issue. Thank you very much!

Are the ManagedObjectRealtimeService, MeasurementRealtimeService, EventRealtimeService not provided by default as i can normally use them in a component for example without explicitly providing them somewhere.
Where exactly is the difference here?

These services are not provided by default as a global singleton (providedIn: 'root'), as they are meant to be provided on component level. They provide start and stop methods which would enable/disable realtime. The idea is that start and stop could be called by the component when ever it’s realtime indicator (usually found in the action bar) is toggled. Toggling realtime should not affect all realtime connections, but only the ones that belong to the current component. This is why we are having an instance of that service per component instead of a global singleton.

Totally makes sense.
Thank you very much for the explanation.