How to inject AngularJS dependencies while calling from Angular module - angularjs

I am trying to host a simple hybrid application (AngularJS + Angular 7). I was able to set the basic and bootstrap both the modules based on the guidelines. I was able to establish the communication from AngularJS to Angular.
I followed the guidelines for injecting AngularJS service in the Angular module ( https://angular.io/guide/upgrade ). However, I see there are few issues that I am stuck with which could be a basic set up which I am trying to understand.
Here is the real issue, When I try to call the AngularJS service from Angular, it fails during the run time stating the dependency injected in AngularJS service is undefined. For ex: $http is not defined.
My question is, do I need to inject this dependency through a provider in the Angular module, I have a provider which injects the service itself to the Angular module. Any help with the guidelines or standards would be helpful.
Here is the format of my AngularJS service. At present, I am modifying this to class and trying to invoke from Angular
function GetUsers( $http, OtherDependency) {
return {
getUsers: getUsers
}
function getUsers(userID, key, phrase) {
//$http is used inside this method
}
In angular, I am injecting this service through a provider and trying to use this service by instantiating through its constructor

Since your stackbliz demo is not runnable, from my project, I just create a Angular SharedModule and inject angularjs services to provider like below:
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
export function getToastrService($injector: any) {
return $injector.get('toastr');
}
export function getLoadingService($injector: any) {
return $injector.get('loadingService');
}
#NgModule({
imports: [
CommonModule
],
exports: [
NgxComponentsModule
],
providers: [
{ provide: 'toastr', deps: ['$injector'], useFactory: getToastrService },
{ provide: 'loadingService', deps: ['$injector'], useFactory: getLoadingService }
]
})
export class NgxSharedModule { }
And then in my Angular business module, import SharedModule, and then only need to use DI in constructor:
constructor(#Inject('toastr') public toastr: any,
private notifyService: NotifyService) { }
Hope it will work for you.

Related

DowngradeModule and downgradeInjectable result in singleton service per module in Angular/AngularJS hybrid app instead of global singleton

The situation:
I am working on a hybrid project with AngularJS as core where the app slowly needs to be upgraded to Angular 9. So the new Angular modules have to be downgraded to be accessable to AngularJS. The AngularJS app consists of multiple modules and submodules. Here just the core(shared) module and the app.module for simplification:
core.ng2.module.ts
#NgModule({
imports: [
CommonModule
],
declarations: [
ModuleBootstrapComponent
],
entryComponents: [
ModuleBootstrapComponent
]
})
export class CoreNg2Module {
constructor() {
}
public ngDoBootstrap(): void {
}
}
let rootInjectorPromise: Promise<Injector>| null = null;
const getRootInjector = (extraProviders: StaticProvider[]) => {
if (!rootInjectorPromise) {
rootInjectorPromise = platformBrowserDynamic(extraProviders)
.bootstrapModule(AppNg2Module)
.then(moduleRef => moduleRef.injector);
}
return rootInjectorPromise;
};
const bootstrapCoreFn = async (extraProviders: StaticProvider[]) => {
const rootInjector = await getRootInjector(extraProviders);
const moduleFactory = await rootInjector.get(Compiler).compileModuleAsync(CoreNg2Module);
return moduleFactory.create(rootInjector);
};
const downgradedCoreNg2Module = downgradeModule(bootstrapCoreFn);
export const coreNg2ModuleName =
angular
.module('core.ng2', [
downgradedCoreNg2Module
])
.directive('moduleBootstrap',
downgradeComponent({
component: ModuleBootstrapComponent,
downgradedModule: downgradedCoreNg2Module
}))
.name;
the app.module.ts
#NgModule({
imports: [
BrowserModule,
CommonModule,
CoreNg2Module,
]
})
export class AppNg2Module {
constructor() {
}
public ngDoBootstrap(): void {
// Don't use this lifecycle hook since the hybrid app is bootstrapped manually inside Bootstrap.ts.
}
}
export const appModuleName = angular
.module('app.ng2', [
coreNg2ModuleName,
]).factory('translateSyncService',
downgradeInjectable(TranslateSyncService))
.name;
The current problem:
I downgraded and bootstraped the module according to the official guide: https://angular.io/api/upgrade/static/downgradeModule#downgrading-multiple-modules to have my app.module as 'root' for my TranslateSyncService. Because according to this https://angular.io/guide/upgrade#how-ngupgrade-works singleton services are shared between AngularJS and Angular IF they are provided in 'root'.
I need the translate-sync-service to synchronize the state of my Angular components with the AngularJS app.
#Injectable({
providedIn: 'root'
})
export class TranslateSyncService {
languageSource = new BehaviorSubject<string>('en');
usedLanguage$ = this.languageSource.asObservable();
constructor() {
}
changeLanguage(language: string) {
this.languageSource.next(language);
}
}
I downgraded the module and service to make it accessable in AngularJS. The problem is that the service is working, but every module gets its own instance of the service instead of a global singleton service. So core module has its own service and every other module I created.
tldr; I end up with a singleton service per module instead of a global one.
My wish:
I want to have an Angular service that is a singleton across the downgraded Angular modules and the AngularJS app.
Anyone has experience with that issue?
I know this is old but I had the same issue. I found an answer that worked for me (similar situation). We use a common pattern of a downgraded module that we inject into the angular JS app. (there are quite a few examples of this elsewhere).
export const downgradedAngularAppModule = angular.module('downgraded-angular-module', [downgradedAngularModule])
.service('SomeAngularXService', downgradeInjectable(SomeAngularXService))
and then in angularJS
export const angularJsModule: ng.IModule = angular.module(angularjsAppModule, [list of angularJS modules, downgradedAngularAppModule.name])
While still in hybrid mode, we are bootstrapping angular inside angular JS. In the singleton angular X service ensure you decorate with:
#Injectable(
{ providedIn: 'root' } // this means app wide singleton
)
If you wish to use the same instance of the service that you have in your angularJS app, in components that you have migrated to angular X, rather than importing the module directly like this:
import { SomeAngularXService } from 'SomeAngularXService.service.ts'
and injecting it in your constructor:
constructor(private someAngularXService: SomeAngularXService)
you should instead use the angularJS instance in your constructor
constructor(#Inject('SomeAngularXService') private someAngularXService: any)
You will need to remember to provide it in your app.module.ts
{
provide: 'SomeAngularXService',
useFactory: ($injector: any) => $injector.get('SomeAngularXService'),
deps: ['$injector']
}

Using services in Angular/AngularJS hybrid app (ng-upgrade)

Currently building a slimmed down version of the app for plunker so I can SHOW you my problem, but in case anyone has any tips off the top of their heads in the mean time, I will attempt to describe my problem first.
I'm using ngUpgrade to start bringing a large application from AngularJS to Angular. I've got a core application running using Angular. Briefly it's set up a little like this:
#Component({
selector: '[my-app]',
template: `
<app-main></app-main>
`
})
export class AppComponent {};
#NgModule({
imports: [
BrowserModule,
UpgradeModule,
],
bootstrap: [AppComponent],
declarations: [
AppComponent
],
schemas: [
CUSTOM_ELEMENTS_SCHEMA
]
})
export class AppModule {
constructor(public upgrade: UpgradeModule) { }
}
export const Ng1AppModule = angular.module(
'mainApp',
[
'feature.one'
]
);
platformBrowserDynamic().bootstrapModule(AppModule).then(ref => {
ref.instance.upgrade.bootstrap(document.body, [Ng1AppModule.name], {});
});
It successfully bootstraps itself and runs a root component which essentially loads the old AngularJS application. So far no major problems.
The AngularJS application has dependencies on a lot of custom feature modules which I now need to convert to Angular.
On one of the feature modules I want to convert a service. It's now an Angular #Injectable built in typescript and it is assigned to and AngularJS module like so:
export const Ng1FeatureModule = angular
.module('feature.one', ['ngCookies'])
.service('UpgradedService', downgradeInjectable(UpgradedService) as any);
This service requires a dependency from a service I have not even converted yet.
Example:
#Injectable()
export class UpgradedService{
public var1: string;
constructor(private nonconvertedNG1Service: NonconvertedNG1Service) {
this.var1 = nonconvertedNG1Service.get();
}
public getVar1() {
return this.var1;
}
}
How do I need to set things up so that my example app uses 'UpgradedService' and UpgradedService is able to use NonconvertedNG1Service?

How to use 3rd party AngularJS (1.6) module (angular-translate) in Angular (2 or 4)

The case
I am in the process of upgrading an AngularJS (i.e. Angular 1.6) application to Angular (i.e. Angular 4.1.3). I chose to do the incremental upgrade so currently both AngularJS and Angular are bootstrapped and running. So far so good, but: One of the AngularJS services that should be rewritten to Angular relies on a well known service $translate which is part of the 3rd party AngularJS (read Angular 1) module pascalprecht.translate. In other words, $translate service is injected to MyService which should become an Angular (read Angular 4) service.
MyService (stripped down) looks like this:
import { Injectable } from '#angular/core';
#Injectable()
export class MyService {
// Here comes the injection (how to do it?).
constructor(private $translate: $translate) {
}
foo() {
// Use the service
this.$translate('HELLO_WORLD');
}
}
It is located within MyModule:
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
#NgModule({
providers: [
MyService
]
})
export class MyModule {
}
The problem
Now, how can I inject $translate into MyService when MyService resides within an Angular module while $translate is part of a 3rd party AngularJS module?
I know how to inject an AngularJS service into an Angular service if the AngularJS service is located within the same module (or at least the module is part of my own solution). It is explained in the official docs. Is there any way to handle a 3rd party service? Do I need to register that service within MyModule's providers?
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
// How this line should look line then?
import { $translate } from 'node_modules/angular-translate/...';
#NgModule({
providers: [
MyService,
$translate
]
})
export class MyModule {
}
Or am I trying to achieve impossible?
Well, after a few hours of struggling I've finally found the solution. Here it is:
Upgrade the 3rd party service
First of all, follow the Angular's official guidelines and upgrade the provider of the 3rd party service - $translate. Like this:
ajs-upgraded-providers.ts
import { InjectionToken } from '#angular/core';
import { downgradeInjectable } from '#angular/upgrade/static';
import 'angular-translate';
// Variable 'i' holds the standard AngularJS injector
export function $translateFactory(i: any) {
return i.get('$translate');
};
// There is no class representing the good old $translate service so we have
// a non-class dependency. Therefore we use an InjectionToken (Angular 4) or
// OpaqueToken (Angular 2).
export let $translate = new InjectionToken('$translate');
// Finally create the upgraded provider
export const $translateProvider = {
provide: $translate,
useFactory: $translateFactory,
deps: ['$injector']
};
One thing to notice, the $translate service might be dependent on other old AngularJS services like (in my case) $translateStaticFilesLoader or $translateMessageFormatInterpolation. If this is also your case, be sure to extend the code above and make upgraded providers for those services as well (keep them in the same file).
Make sure the import statement works
The angular-translate is installed as a node module so the statement
import 'angular-translate';
works just fine if your tsconfig.json is set up to use moduleResolution: "node".
Then, of course, you need to ensure that the import statement will work even after the code is transpiled from TypeScript to ES5 (or whichever target you use) and picked up by a module loader (SystemJS in my case).
Notice that we imported the angular-translate script without getting anything from it, just to cause side effects. Basically, the import ensures that the script containing the desired service $translate is simply executed and registers $translate service for the AngularJS $injector.
Register the upgraded provider in Angular module
Now ensure that the new upgraded provider of $translate service is registered among other providers of MyModule.
my.module.ts
import { NgModule } from '#angular/core';
import { MyService } from './my.service';
import { $translateProvider } from './ajs-upgraded-providers';
#NgModule({
providers: [
MyService,
$translateProvider
]
})
export class MyModule {
}
Use it
Finally, use the $translate service in MyService.
my.service.ts
import { Injectable, Inject } from '#angular/core';
import { $translate } from './ajs-upgraded-providers';
#Injectable()
export class MyService {
// Notice the usage of InjectionToken
constructor(#Inject($translate) private $translate: any) {
}
foo() {
this.$translate('hello.world').then((translation: string) => {
console.log(translation);
});
}
}

Migration to Angular2: How to use injected AngularJS dependencies

How Am I supposed to use an AngularJS dependency during migration to Angular2 outside the constructor? I am using upgrade module and service is not yet upgraded.
So the answer was partially Making AngularJS Dependencies Injectable to Angular but it was not demonstrated how to make it available outside the constructor.
Here is an example for the $log angularJS service.
Create ajs-upgraded-providers.ts where we declare the provider:
/**
* $log upgraded provider
*/
export abstract class Log {
[key: string]: any;
}
export function logFactory(i: any) {
return i.get('$log');
}
export const logProvider = {
provide: Log,
useFactory: logFactory,
deps: ['$injector']
};
Import to app.module.ts the declared provider:
import { logProvider } from './ajs-upgraded-providers';
#NgModule({
imports: [
//imports
],
providers: [
//Providers & Services
//Upgraded angularJS providers
logProvider
]
})
example.service.ts How to use the angularJS service while migration takes place:
import { Log } from '../ajs-upgraded-providers'; //edit path accordingly
#Injectable()
export class ExampleService {
constructor(private $log: Log) {}
exampleFunction() {
this.$log.info("Info to be logged");
}
}
Making AngularJS Dependencies Injectable to Angular
When running a hybrid app, we may bump into situations where we need to have some AngularJS dependencies to be injected to Angular code. This may be because we have some business logic still in AngularJS services, or because we need some of AngularJS's built-in services like $location or $timeout.
In these situations, it is possible to upgrade an AngularJS provider to Angular. This makes it possible to then inject it somewhere in Angular code. For example, we might have a service called HeroesService in AngularJS:
import { Hero } from '../hero';
export class HeroesService {
get() {
return [
new Hero(1, 'Windstorm'),
new Hero(2, 'Spiderman')
];
}
}
We can upgrade the service using a Angular Factory provider that requests the service from the AngularJS $injector.
We recommend declaring the Factory Provider in a separate ajs-upgraded-providers.ts file so that they are all together, making it easier to reference them, create new ones and delete them once the upgrade is over.
It's also recommended to export the heroesServiceFactory function so that Ahead-of-Time compilation can pick it up.
— Angular Developer Guide - Upgrading (Making AngularJS Dependencies Injectable)

What is the equivalent of a factory in Angular2?

So I am used to using factories & services in Angular.
I am reading through the Angular2 docs and I don't see any equivalent of a factory. What is the equivalent for Angular2?
Factories, services, constants and values are all gone in Angular2. Angular2 is radically and fundamentally different from the classic Angular. In Angular2, the core concepts are
components
dependency injection
binding
The idea of services, factories, providers and constants has been criticized in Angular 1. It was difficult to choose between one. Removing them simplifies things a bit.
In the original Angular, you would define a service like so
app.service('BookService', ['$http', '$q', BookService]);
function BookService($http, $q){
var self = this;
var cachedBooks;
self.getBooks = function(){
if (cachedBooks) {
return $q.when(cachedBooks);
}
return $http.get('/books').then(function(response){
cachedBooks = response.data.books;
return cachedBooks;
})
}
}
Angular2 significantly leverages ES6 syntax to make the code more readable and easier to understand.
One new keyword in ES6 is class, which can be thought of as a service.
ES6 classes are a simple sugar over the prototype-based OO pattern. Having a single convenient declarative form makes class patterns easier to use, and encourages interoperability. Classes support prototype-based inheritance, super calls, instance and static methods and constructors.
Here's how that same code might look in Angular2
import {HttpService, Promise} from '../Angular/Angular2';
export class BookService{
$http, $q, cachedBooks;
constructor($http: HttpService, $q: Promise) {
this.$http = $http;
this.$q = $q
}
getBooks() {
if (this.cachedBooks) {
return this.$q.when(this.cachedBooks);
}
return this.$http.get('/books').then(function(data) {
this.cachedBooks = data.books;
return this.cachedBooks;
})
}
}
#Richard Hamilton's answer is appreciated and in addition to that there are few points to note.
For Factories,Service, and etc, in Angular2 we have service (or shared service). we have to make our service Injectable in order to use it.
NOTE: This code belongs to beta version and not RC.
import {Component, Injectable,Input,Output,EventEmitter} from 'angular2/core'
import {Router} from 'angular2/router';
import {Http} from 'angular2/http';
export interface ImyInterface {
show:boolean;
}
#Injectable() <---------------------------- Very Important
export class sharedService { <----------------- Service Name
showhide:ImyInterface={show:true};
constructor(http:Http;router:Router)
{
this.http=http;
}
change(){
this.showhide.show=!this.showhide.show;
}
}
If I want to use everywhere in my app, then I have to inject this service in bootstrap function like this,
bootstrap(App, [HTTP_PROVIDERS,sharedService <--------Name Injection
ROUTER_PROVIDERS,bind(APP_BASE_HREF).toValue(location.pathname)
]);
This way it creates single instance of your service. If you don't want to go with single instance, what you can do is - you can use Providers:[sharedService] metadata in you #component decorator.
Then, use it in your one of components like this,
export class TheContent {
constructor(private ss: sharedService) { <--------Injection dependency of your newly created service
console.log("content started");
}
showhide() {
this.ss.change(); <----- usage
}
}
Check working example here
I don't know what factories do exactly in Angular1 but in Angular2 there is useFactory:
{
provide: SomeClass,
useFactory: (dep1, dep2) => (x) => new SomeClassImpl(x, dep1, dep2),
deps: [Dep1, Dep2]
}
to provide your own instance construction logic if the default doesn't match your needs.
You can also inject a factory to create new instances yourself:
/* deprecated or removed depending on the Angular version you are using */
provide(SomeClass, {
useFactory: (dep1, dep2) => {
(x) => new SomeClassImpl(x, dep1, dep2),
},
deps: [Dep1, Dep2]
})
constructor(#Inject(SomeClass) someClassFactory: any) {
let newSomeClass = someClassFactory(1);
}
Argument x must have type assignment, otherwise angular doesn't know how to deal with it.
class SomeClassImpl {
constructor(x: number, dep1: Dep1, dep2: Dep2){}
}
If you need a new instance of a service in some component you need to provide it in that component like this:
#Component({
selector: 'hero-list',
templateUrl: './hero-list.component.html',
providers: [ HeroService ]
})
This will generate a new instance of the HereService as a factory does.

Resources