1

Problem Summary

I am migrating an old AngularJS application to use Webpack and I am splitting it into three parts, which I want to represent as AngularJS modules:

  • src-ts-admin
  • src-ts-common
  • src-ts-store

For example I'd like 'src-ts-admin' to consume services from 'src-ts-common', but unfortunately webpack bundles files in the wrong order for AngularJS Dependency Injection, so that I end up with errors like ..

Error: "[$injector:unpr] Unknown provider: filterServiceProvider <- filterService <- initializerService

.. where initializerService is a service from src-ts-admin and filterService is from src-ts-common. The core of the problem is that

  • module("ADMIN").service("initializerService") executes before
  • module("COMMON).service("filterService")

.. although I cannot spot the problem in the dependencies.

File Overview

src-ts-admin: app.ts

This is the webpack entry point, however not so interesting. The application routing will trigger app.initializer.ts.

import angular from 'angular';
import { appModule } from './app.module';

src-ts-admin: app.module.ts

import angular from 'angular';
import commonModuleName from '../src-ts-common/common.module';

var modulezz;
modulezz = [commonModuleName,'ngRoute', 'ui.router', 'ui.multiselect', 'ui.bootstrap', 'ui.bootstrap-slider', 'ui.bootstrap.buttons', 'duScroll',
            'ngMessages', 'colorpicker.module', 'angular-bind-html-compile', 'pascalprecht.translate'];

export var appModule = angular.module("ADMIN", modulezz);
export default appModule.name;

src-ts-admin: app.initializer.ts

import angular from "angular";
import appModuleName from "./app.module";
import { filterService } from "../src-ts-common/service/filter-service-model";

export class InitializerController {
    static $inject = ...etc...
    constructor(...etc...) {

    /* THIS IS KICKED OFF BY AN ANGULAR ROUTE, THIS KICKS OFF THE InitializerController BELOW */

    }
}
angular.module(appModuleName).controller("initializerController", InitializerController);

export class InitializerService {
    static $inject = ["$stateParams", "$window", "$timeout", "filterService", ...etc...];
    constructor(private $stateParams, private $window, private $timeout, private filterService: filterService, ...etc...) {

        /* THIS IS WHERE THE DEPENDENCY INJECTION FAILS */

    }
}
angular.module(appModuleName).service("initializerService", InitializerService);

src-ts-common: common.module.ts

import angular from 'angular';
export default angular.module("COMMON",[]).name;

src-ts-common: filter.service.ts

import angular from "angular";
import commonModuleName from '../common.module';

export class filterService {
    static $inject = [];
    constructor() {
    /* ...simplified... */
    }
}
angular.module(commonModuleName).service("filterService", filterService);

My thoughts

The error I get is ..

Error: "[$injector:unpr] Unknown provider: filterServiceProvider <- filterService <- initializerService

.. which indicates to me that app.initializer.ts is executed before filter.service.ts, although it is importing that file.

Do you see any obious errors I made? Do you have any best practices to structure AngularJS + Webpack module imports and cut files?

--- Solution (Update!) ---

Thanks to https://stackoverflow.com/a/59631154/5244937 I could solve my problem. The pattern I should have used was to initialize the angular modules (e.g. angular.module(commonModuleName).service("filterService", filterService);) in the app.module.ts and common.module.ts files and NOT together with their class implementations.

Here the corrected files:

src-ts-admin: app.ts

/* here i am more or less just kicking off the angular application after i import app.module.ts */
import { appModule } from './app.module';

appModule.run(["$rootScope", "$location", ...,
    function ($rootScope, $location, ...) {
  ..
}

appModule.controller('appController', ['$scope', ..,
    function ($scope: any, ...) {
  ..
}

src-ts-admin: app.module.ts

/** here (1) i am initializing all angular controllers, services, factories from the ADMIN package & (2) importing the COMMON module  */

import angular from 'angular';
import ngRoute from 'angular-route';
import uiRouter from 'angular-ui-router';
import 'angular-messages';
import 'angular-translate';
import dptCommonModuleName from '../src-ts-common/common.module';

var modulezz;
modulezz = [dptCommonModuleName, ngRoute, uiRouter, 'ui.multiselect', 'ui.bootstrap', 'ui.bootstrap-slider', 'ui.bootstrap.buttons', 'duScroll',
        'ngMessages', 'colorpicker.module', 'angular-bind-html-compile', 'pascalprecht.translate'];

export var appModule = angular.module("DPT", modulezz);
export var appModuleName = appModule.name;

/**
 * Services
 */
angular.module(appModuleName).service("systemParameterService", systemParameterService);
angular.module(appModuleName).service("statisticsService", statisticsService);
angular.module(appModuleName).service("deletionCheckService", deletionCheckService);
...

src-ts-admin: app.initializer.ts

/** here i have removed (1) the import of angular.module.ts and (2) the initialization of angular controller and service "initializerController" and "initializationService" */

import angular from "angular";
import { filterService } from "../src-ts-common/service/filter-service-model";

export class InitializerController {
    static $inject = ...etc...
    constructor(...etc...) {

    /* THIS IS KICKED OFF BY AN ANGULAR ROUTE, THIS KICKS OFF THE InitializerController BELOW */

    }
}

export class InitializerService {
    static $inject = ["$stateParams", "$window", "$timeout", "filterService", ...etc...];
    constructor(private $stateParams, private $window, private $timeout, private filterService: filterService, ...etc...) {

        /* THIS IS WHERE THE DEPENDENCY INJECTION FAILS */

    }
}

src-ts-common: common.module.ts

/* here i have added the initialization of the angular service filterService and all other services, factories, .. contained in that module */

var moduleName = angular.module("DPT.COMMON",[]).name
export default moduleName;

angular.module(moduleName).factory("filterModel", function () { return filterModel; });
angular.module(moduleName).factory("filterPresetModel", function () {return filterPresetModel;});
angular.module(moduleName).factory("productModel", function () {return productModel;});
angular.module(moduleName).factory("propertyProfileModel", [function () {return propertyProfileModel;}]);
angular.module(moduleName).factory("propertyValueModel",function () {return propertyValueModel;});

src-ts-common: filter.service.ts

/** here i have (1) removed the import of common.module and (2) teh initialization of angular.module(..).service("filterService", ..) */

import angular from "angular";

export class filterService {
    static $inject = [];
    constructor() {
    /* ...simplified... */
    }
}
2
  • Please, how are you able to import import angular from "angular". I am having challenges importing angularjs v1.3.7 Commented Aug 5, 2021 at 12:36
  • @Orson You need to install angular types: npm install @types/angular1 Commented Jun 1, 2023 at 10:17

1 Answer 1

1

Your dependencies are inverted, Module should import its deps (other modules or filters / services / components).

If you will follow this, your AppModule will import its child modules, which in it turn will import its services.

That means that your services should only export the class. The relevant Module should import the service class, and register it on the module & export the Module name. The parent Module should import its child Modules.

// serviceX.js

export class serviceX {
  static $inject=[];
  ...
}
// childModule.js

import {serviceX} from './services/serviceX.js';
import {componentY} from './components/componentY.js';
import angular from 'angular';

export default angular.module('childModule', [])
.service('serviceX', serviceX)
.component('componentY', componentY)
// ... rest of the childModule deps
.name;
// appModule.js

import {externalDep} from 'external-dep';
import childModule from './modules/childModule';
import angular from 'angular';

const appModule = anguler.module('appModule', [externalDep, childModule]).name;

angular.bootstrap(document, [appModule]);
Sign up to request clarification or add additional context in comments.

1 Comment

You are right. Together with the tutorial below, I have corrected the structure of the imports and files. angular-tips.com/blog/2015/06/… I'll update my original post with my current state

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.