8

I'm working through Angular's upgrade guide to learn how to embed AngularJS components in an Angular app. I've created a bare-bones Angular app using the Angular CLI and added a simple AngularJS module as a dependency.

When I run ng serve, the application compiles with no errors. However, at runtime, I get this message in the console:

Error: Trying to get the AngularJS injector before it being set.

What is causing this error, and how can I avoid it? I haven't deviated from the steps detailed in the upgrade guide.

Here's how I'm upgrading my AngularJS component inside my Angular app:

// example.directive.ts
import { Directive, ElementRef, Injector } from '@angular/core';
import { UpgradeComponent } from '@angular/upgrade/static';

// this is the npm module that contains the AngularJS component
import { MyComponent } from '@my-company/module-test';

@Directive({
    selector: 'my-upgraded-component'
})
export class ExampleDirective extends UpgradeComponent {
    constructor(elementRef: ElementRef, injector: Injector) {

        // the .injectionName property is the component's selector
        // string; "my-component" in this case.
        super(MyComponent.injectionName, elementRef, injector);
    }
}

And here's my app.module.ts:

// app.module.ts
import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { UpgradeModule } from '@angular/upgrade/static';
import { ExampleDirective } from './example.directive';
import { myModuleName } from '@my-company/module-test';

@NgModule({
    declarations: [AppComponent, ExampleDirective],
    imports: [BrowserModule, AppRoutingModule, UpgradeModule],
    providers: [],
    bootstrap: [AppComponent]
})
export class AppModule {
    constructor(private upgrade: UpgradeModule) {}
    ngDoBootstrap() {
        this.upgrade.bootstrap(document.body, [myModuleName], {
            strictDi: true
        });
    }
}

I'm using Angular 5.2.0.

Nathan Friend
  • 12,155
  • 10
  • 75
  • 125
  • Why is the import statement different for each file when referencing `@my-company/module-test`? It also doesn't look like you do anything with the imported `MyComponent` in the first file. – Pop-A-Stash Apr 02 '18 at 16:17
  • @Pop-A-Stash - I was simplifying the example a bit, but I've updated my question to show why I import the component; it's so that I can reference the component's selector string which is stored as a property on the component class. The two files import different things from `@my-company/module-test` because the module exports multiple named exports. – Nathan Friend Apr 02 '18 at 16:26
  • 1
    I've opened an issue on Angular's GitHub here: https://github.com/angular/angular/issues/23141 – Nathan Friend Apr 03 '18 at 13:29
  • 1
    i have same issue https://stackblitz.com/edit/angular-pyvy53 – Amin Rahimi Apr 06 '18 at 06:49
  • I got this error when I imported the `UpgradeModule` in more than one NgModule. Once I removed that, it went away. – Carlo Bos Aug 23 '19 at 21:47
  • @Amin Rahimi: You need to and tags to your Stackblitz html and place the script references to (jquery &) angular in the section. – Kieran Ryan Jul 04 '21 at 18:03

3 Answers3

9

I faced the same issue, and finally solved it. There are some steps to follow before bootstrap an hybrid Angular/angularjs application.

  1. Install the UpgradeModule npm install @angular/upgrade

  2. Wrap your "CompanyModule" (the module where all your company components are registered) into a new angularjs module (for instance: Ng1Shared). If you not have a module for your company components, it must be created. Than downgrade AppComponent as shown below.

    const MyCompanyModule = angular
       .module('MyCompanyModule', [])
       .component('myComponent', MyComponent)
       .name;
    
    
    const Ng1Shared = angular
       .module('Ng1Shared', [MyCompanyModule])
       .directive('appRoot', downgradeComponent({ component: AppComponent }))
       .name; 
    
  3. Configure AppModule with basic imports (BrowserModule, CommonModule, UpgradeModule). Provide the angularjs' Injector to Angular; declare an "entryComponent" and remove the default bootstrap for AppComponent.

    @NgModule({
      imports: [BrowserModule, CommonModule, UpgradeModule], 
      declarations: [AppComponent], 
      providers: [{provide: '$scope', useExisting: '$rootScope'}], // REQUIRED
      entryComponents: [AppComponent], // ADD AN ENTRY COMPONENT 
      // bootstrap: [AppComponent] MUST BE REMOVED
    })
    
  4. Set angularjs globally with a function provided by UpgradeModule itself and manually bootstrap Angular with DoBootstrap method provided by @angular/core.

    export class AppModule implements DoBootstrap { 
      constructor(private upgrade: UpgradeModule) { }
    
      public ngDoBootstrap(app: any): void {
        setAngularJSGlobal(angular);
        this.upgrade.bootstrap(document.body, [Ng1Shared], { strictDi: false });
        app.bootstrap(AppComponent);
      }
    }
    
  5. Create a wrapper directive for every angularjs component and add it to AppModule's declaration array.

    @Directive({
        selector: 'my-component'
    })
    export class MyComponentWrapper extends UpgradeComponent {
        @Input() title: string;
    
        constructor(elementRef: ElementRef, injector: Injector) {
          super('myComponent', elementRef, injector);
        }
    }
    

I wrote a simple example available on stackblitz. For example purposes I added angularjs MyCompanyModule to another angularjs module, called Ng1Module. As you can see also property binding between angularjs and angular component works fine.

I hope it can be useful.

lucaleone
  • 161
  • 2
  • 5
0

https://github.com/angular/angular/issues/23141#issuecomment-379493753

you cannot directly bootstrap an Angular component that contains upgraded components before bootstrapping AngularJS. Instead, you can downgrade AppComponent and let it be bootstrapped as part of the AngularJS part of the app:

https://stackblitz.com/edit/angular-djb5bu?file=app%2Fapp.module.ts

Amin Rahimi
  • 235
  • 3
  • 7
0

try to add an entryComponents to your AppModule like this :

...
@NgModule({
    declarations: [AppComponent, ExampleDirective],
    imports: [BrowserModule, AppRoutingModule, UpgradeModule],
    entryComponents: [
          AppComponent // Don't forget this!!!
    ],
    providers: [],
    // bootstrap: [AppComponent] // Delete or comment this line 
})
...
Ridae HAMDANI
  • 686
  • 2
  • 7
  • 17
  • With Ivy engine, this is no longer needed https://angular.io/guide/deprecations#entrycomponents-and-analyze_for_entry_components-no-longer-required – cloakedninjas May 18 '22 at 06:46