Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Error when using AngularJS component inside Angular app: "Error: Trying to get the AngularJS injector before it being set."

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.

like image 704
Nathan Friend Avatar asked Apr 02 '18 16:04

Nathan Friend


1 Answers

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.

like image 91
lucaleone Avatar answered Oct 19 '22 06:10

lucaleone