I'm using angular. I already know that when an appmodule is importing modules which declares providers, the root injector gets them all and the service is visible to the app - globally. (I'm not talking about lazy loaded modules)
But is it possible that each module will have its own instance of the service?
I thought of maybe something like this :
#NgModule({
providers: [AService]
})
class A {
forRoot() {
return {
ngModule: A,
providers: [AService]
}
}
forChild() {
return {
ngModule: A,
providers: [AService]
}
}
}
But I don't know if it's the right way of doing it
Question
How can I accomplish service per module ?
STACKBLITZ : from my testing , they are using the same service instance
When we provide a service in a feature module that is eagerly loaded
by our app's root module, it is available for everyone to inject. - John Papa
So looks like there is no way to inject at feature-module level, as there are no module level injectors other than the one at root module.
But angular has nodes at each component level for the injector, so such a scenario will have to use coponent level-injectors I guess.
You can also have a parent component inject the service for different children sharing the same instance.
One way is to provide the services at component level. Not sure if that will work for you.
Also, check the multiple edit scenario in the docs
https://angular-iayenb.stackblitz.io
import { Component, OnInit } from '#angular/core';
import {CounterService} from "../../counter.service"
#Component({
selector: 'c2',
templateUrl: './c2.component.html',
styleUrls: ['./c2.component.css'],
providers:[CounterService]
})
export class C2Component implements OnInit {
constructor(private s:CounterService) { }
ngOnInit() {
}
}
Question
How can I accomplish service per module ?
Not with the default Injector. Default Injector keeps nodes at root level and component level, not at feature-module level. You will have to have a custom Injector if there is a real scenario.
Edit: the previous answer from me is not completely correct. The correct way to make this work is to use lazy loaded modules, provide the services there. The given service should not be provided with a static forRoot() method somewhere because then the lazy loaded module will access the root injector.
There is no actual reference to do so because that is not how angular is designed but if you want it that way you have to the opposite of
https://angular.io/guide/singleton-services#providing-a-singleton-service
and
https://angular.io/guide/singleton-services#forroot
Old not completely correct:
You simple have to declare the service you want to be single instance for each and every module within the providers meta data of each and every module. Then Angular will not reuse any instance of the service.
Giving scenario: You have two modules, ModuleA and ModuleB, both need the service but different instance, then you will declare them in the providers section of ModuleA and ModuleB.
Reference:
https://angular.io/guide/singleton-services
Three Modules as depicted...
|Elements| and |myForms| are imported into |App|.
Folder Structure
--app
|--Elements angular module (has ConfigService in it).
|--App angular module (angular imports Elements, and myForms (NPM PKG))
Elements module exports ConfigService using forRoot.
export class ElementsModule {
static forRoot(): ModuleWithProviders {
return {
ngModule: ElementsModule,
providers: [ConfigService]
};
}
}
App module imports Elements and myForms modules.
import { Elements } from './local-dir';
import { myForms } from 'NPM PACKAGE';
#NgModule({
imports: [
Elements.forRoot(),
myForms
]
})
How would I give myForms access to the ConfigService in a component?
The forRoot method is working as I have access in the App module like so.
import { ElementsModule, ConfigService } form './local-dir';
I would like to give it to a myForms service that myForms already knows about. Or find a way in which I don't have to directly import the file into the myForms component I want to use it in.
If I go into the myForms NPM PACKAGE, I can do the following and it will work of course.
import { ConfigService } from './local-dir';
#Component({
selector: 'my-element',
providers: [ConfigService]
});
export class MyElementComponent {
constructor(private CS: ConfigService){}
}
Update - further clarification.
There is one app module.
The app module imports a myForms module. This module requires info from another module that will also be loaded.
The other module that will be loaded, is one of many that will be choses from. But in the end, only one will be chosen.
when the chosen module shows up in the app module, I then want to share something it has with the myForms module, inside of the app module. This is a one time share, the myForms modules does not require communication after this.
The chosen module hands over a key/value of { anyKey: chosenModuleClassNamesToken }.
Then in the myForms module, I can call the shared data to get class names from the chosen module like this: myFormsService[ chosenModuleDataKey ]. This would result in a value of a class from the chosen module, that I would then build in the view using ComponentFactoryResolver.
Create a barrel file index.ts
export * from '....' path to your module
export * from '....' path to your service
Assuming the below folder structure,
---modules
|---- elements
|---- configuration
Create a barrel file inside the elements folder naming it as index.ts
export * from './elements.module';
export * from '../configuration/configuration.service';
Use this file to import in a single line as
import { ElementsModule, ConfigService } from '../elements';
With angular 5.0 the upgrade module now has the option of using downgradeModule which runs angularjs outside of the angular zone. While experimenting with this I have run into a problem with using downgradeInjectable.
I am receiving the error:
Uncaught Error: Trying to get the Angular injector before bootstrapping an Angular module.
Bootstrapping angular in angular js works fine
import 'zone.js/dist/zone.js';
import * as angular from 'angular';
/**
* Angular bootstrapping
*/
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { decorateModuleRef } from 'src/environment';
import { AppModule } from 'src/app/app.module';
import { downgradeModule } from '#angular/upgrade/static';
export const bootstrapFn = ( extraProviders ) => {
const platformRef = platformBrowserDynamic( extraProviders );
return platformRef
.bootstrapModule( AppModule )
.then( decorateModuleRef );
};
angular.module( 'app.bootstrap', [
downgradeModule( bootstrapFn ),
] );
However...
Since the bootstrapping takes place after angularjs has been initialized I can no longer get the downgrade injectable working.
Service to be downgraded
import { Injectable, Inject, OnInit } from '#angular/core';
#Injectable()
export class MobileService implements OnInit{
constructor(
#Inject( 'angularjsDependency1' ) public angularjsDependency1 : any,
#Inject( 'angularjsDependency2' ) public angularjsDependency2 : any,
) {}
}
Downgrade injectable attempt
import * as angular from 'angular';
import { downgradeInjectable } from '#angular/upgrade/static';
import { MyService } from 'src/services/myService/myService';
export const myServiceDowngraded = angular.module( 'services.mobileService', [
angularjsDependency1,
angularjsDependency2,
] )
.factory(
'mobileService',
downgradeInjectable( MyService ),
).name;
When "downgradeInjectable( MyService ) runs the angular injector is not available yet since angular hasn't been bootstrapped. Hence the error:
Uncaught Error: Trying to get the Angular injector before bootstrapping an Angular module.
Does anyone have an idea how I might fix this?
Answers in this thread helped me find a solution, but none contains the holy grail:
Creating a service-boostrap component aside the app's code does not work, because Angular is loaded asynchronously, unlike AngularJS. This gives the same error Trying to get the Angular injector before bootstrapping an Angular module.
Creating a service-bootstrap component wrapping the AngularJS code kind of worked, but then I experienced issues with change detection inside Angular composants, as described in this issue on github.
In the github issue, someone suggested to edit #angular/upgrade source code to change a false to true to force components to be created in the Zone. But in this case it seems to cause performance issues (it seemed to launch ngZone's code multiple times on user events)
In order for the app to work correctly, I needed :
Not to have ng components containing AngularJS components containing Angular components. We need to only have AngularJS containing Angular components.
Make sure that AngularJS components using Angular services are created after a first angular component, named service-bootstrap
To acheive this, I created a slightly modified service-bootstrap component:
import { Component, Output, EventEmitter, AfterViewInit } from "#angular/core";
#Component({
selector: 'service-bootstrap',
template: ``
})
export class ServiceBootstrapComponent implements AfterViewInit{
#Output()
public initialized: EventEmitter<void> = new EventEmitter();
public ngAfterViewInit(){
this.initialized.emit();
}
}
Declared this component as entryComponent in the Angular module and called downgradeComponent to register it in AngularJS:
import { downgradeModule, downgradeInjectable, downgradeComponent } from '#angular/upgrade/static';
const bootstrapFn = (extraProviders: StaticProvider[]) => {
const platformRef = platformBrowserDynamic(extraProviders);
return platformRef.bootstrapModule(AppModule);
};
const downgradedModule = downgradeModule(bootstrapFn);
const app = angular.module('ngApp', [
downgradedModule,
'app'
]);
app.directive('serviceBootstrap', downgradeComponent({ component: ServiceBootstrapComponent }));
Then (and the magic happens here), I created a new AngularJS component:
angular.module("app")
.directive('ng1App', ng1AppDirective);
function ng1AppDirective(){
return {
template: `
<service-bootstrap (initialized)="onInit()"></service-bootstrap>
<section ng-if="initialized">
<!-- Your previous app's code here -->
</section>
`,
controller: ng1AppController,
scope: {},
};
}
ng1AppController.$inject = ['$scope'];
function ng1AppController($scope){
$scope.onInit = onInit;
$scope.initialized = false;
function onInit(){
$scope.initialized = true;
}
}
Then, my index.html only referenced this component
<body>
<ng1-app></ng1-app>
</body>
With this approach, I'm not nesting AngularJS components inside Angular components (which breaks change detection in Angular components), and still I ensure that a first Angular component is loaded before accessing the Angular providers.
Note: The answer below follows the convention of calling angular 1.x as angularjs and all angular 2+ versions as simply angular.
Expanding on JGoodgive's answer above, basically, if you're using downgradeModule, then angular module is bootstrapped lazily by angularjs when it needs to render the first angular component. Until then, since the angular module isn't initialised, if you are accessing any angular services inside angularjs using downgradeInjectable, those services aren't available too.
The workaround is to force bootstrapping of the angular module as early as possible. For this, a simple component is needed:
import {Component} from '#angular/core';
#Component({
selector: 'service-bootstrap',
template: ''
})
export class ServiceBootstrapComponent {}
This component doesn't do anything. Now, we declare this component in the top level angular module.
#NgModule({
// ...providers, imports etc.
declarations: [
// ... existing declarations
ServiceBootstrapComponent
],
entryComponents: [
// ... existing entry components
ServiceBootstrapComponent
]
})
export class MyAngularModule {}
Next, we also need to add a downgraded version of this component to angularjs module. (I added this to the top level angularjs module I had)
angular.module('MyAngularJSModule', [
// ...existing imports
])
.directive(
'serviceBootstrap',
downgradeComponent({ component: ServiceBootstrapComponent }) as angular.IDirectiveFactory
)
Finally, we throw in this component in our index.html.
<body>
<service-bootstrap></service-bootstrap>
<!-- existing body contents -->
</body>
When angularjs finds that component in the markup, it needs to initialise angular module to be able to render that component. The intended side effect of this is that the providers etc. also get initialised and are available to be used with downgradeInjectable, which can be used normally.
This was pointed out to me in an angular github thread.
https://github.com/angular/angular/issues/16491#issuecomment-343021511
George Kalpakas's response:
Just to be clear:
You can use downgradeInjectable() with downgradeModule(), but there are certain limitations. In particular, you cannot try to inject a downgraded injectable until Angular has been bootstrapped. And Angular is bootstrapped (asynchronously) the first time a downgraded component is being rendered. So, you can only safely use a downgraded service inside a downgraded component (i.e. inside upgraded AngularJS components).
I know this is limiting enough that you might decide to not use downgradeInjectable() at all - just wanted to make it more clear what you can and can't do.
Note that the equivalent limitation is true when using an upgraded injectable with UpgradeModule: You cannot use it until AngularJS has been bootstrapped. This limitation usually goes unnoticed though, because AngularJS is usually bootstrapped in the Angular module's ngDoBootstrap() method and AngularJS (unlike Angular) bootstraps synchronously.
I had the same issue, and the reasons are explained in the above answer.
I fixed this by dynamically injecting the downgraded angular service using $injector.
Steps
Register your downgraded service to angularjs module
angular.module('moduleName', dependencies)
angular.factory('service', downgradeInjectable(Service));
Inject $injector to your controller and use this to get the downgraded service
const service = this.$injector.get('service');
service.method();
I had the same issue and it sucked up several hours before finding this.
My workaround was to create a ServiceBootstrapComponent that does nothing but injects all the services that we need to downgrade.
I then downgrade that component, mark it as en entry in #NgModule and add it to index.html.
Works for me.
I was getting the same error in our hybrid app. We are using the following versions:
AngularJS 1.7.x
Angular 7.3.x
As mentioned in this answer, I also used a dummy component called <ng2-bootstrap> to force boostrapping of Angular. And then, I created an AngularJS service which checks if Angular has been bootstrapped:
// tslint:disable: max-line-length
/**
* This service can be used in cases where Angular fails with following error message:
*
* `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`
*
* Above error occurs because of how `downgradeModule` works.
*/
/*#ngInject*/
export class Ng2BootstrapDetectionService {
private bootstrapDone = false;
constructor(private $q: ng.IQService) {}
public whenBootstrapDone(): ng.IPromise<void> {
if (this.bootstrapDone) {
return this.$q.resolve();
}
const deferred = this.$q.defer<void>();
angular.element(document).ready(() => {
const intervalId = setInterval(() => {
const el = document.querySelector('ng2-bootstrap');
if (el && el.outerHTML.includes('ng-version=')) {
this.bootstrapDone = true;
clearInterval(intervalId);
deferred.resolve();
}
}, 500);
});
return deferred.promise;
}
}
Ng2BootstrapDetectionService can be used like below:
import {NotificationService} from 'ng2-app/notification.service';
// This can be used in cases where you get following error:
// `Error: Trying to get the Angular injector before bootstrapping the corresponding Angular module.`
// You will need access to following
// $injector: AngularJS Injector
// Ng2BootstrapDetectionService: our custom service to check bootsrap completion
this.Ng2BootstrapDetectionService
.whenBootstrapDone()
.then(() => {
const notificationService = this.$injector
.get<NotificationService>('ng2NotificationService');
notificationService.notify('my message!');
});
You can find more details about this solution at the end of this blog post.
I'm trying to import the npm module "ajv" into my Ionic 2 (Angular 2) project. (https://epoberezkin.github.io/ajv/)
I ran "npm install ajv --save", then made the following changes to my app.modules.js file:
import { Ajv } from 'ajv';
...
providers : [
Ajv,
...
]
When I compile, however, I get the error:
'Ajv' only refers to a type, but is being used as a value here.
c:/Users/me/Documents/Ionic/MyApp/src/app/app.module.ts
Is there a better way to pull in this library?
The providers array in NgModule is for use with #Injectable components or similarly functional "providers" or "services" in other speak. This library does not appear to conform to that design.
It's likely that all you really want to do is use the import within your application component, rather than in AppModule. By import I mean the simple ES6 style import of "symbol" and then direct usage in code.
So for example in example.component.ts:
import { Component } from '#angular/core';
import { Ajv } from 'ajv';
#Component({
selector: 'app-example',
templateUrl: './example.component.html',
styleUrls: ['./example.component.css']
})
export class ExampleComponent {
myMethod() {
// Just use Ajv here
}
}
No need to put this in the decorator arrays.
If an imported third party library is rather something you want to use in a "service like manner", then by all means wrap it as an #Injectable service, but the consumption will stay much the same as the the above example.
angular.module('app', []).controller('MessagesCtrl', function() {
$scope.self.list = [
{text: 'Hello, World!'},
{text: 'This is a message'},
{text: 'And this is another message'}
];
self.clear = function() {
$scope.self.list = [];
};
});
this is a controller written in angular. how can I convert this into angular 2 using EM6.
well upto my knowledge there are not alot of tutorials for the upgradation but yes thetre are few one.
https://angular.io/docs/ts/latest/guide/upgrade.html
http://blog.thoughtram.io/angular/2015/10/24/upgrading-apps-to-angular-2-using-ngupgrade.html
well let me tell you about basic angular2 app.
in angular 1.x our main module is initilize like this
angular.module('app', [])
but in the angular2 our main component started from the bootstraped file like this.
import {bootstrap} from 'angular2/platform/browser';
import {App} from './app';
bootstrap(App,['here global level dependenices....']);
here app is our main component whihc is imported in this bootstrap file. so bootstraped is file our entry point of the app. and
if we want to do some coding stuff like we work in the angular1.x controller here we do the same work in the class file (typescript class)
here i am posting one basic example like this.
import {Component, View} from 'angular2/core';
#Component({
selector: 'app',
templateUrl: "src/app.html",
styleUrls: ['src/app.css'],
directives: [ directives list here....],
})
export class App
{
// stuff you want to do here
}
firstly we have to import angular2 bundles from the systemjs bundles like we imported Component and view in this example from the angular2/core.
there are alot of imports available for the angular2. you can check out here and here