Requirejs + (multi) angular + typescript

Кажется, я ударился о стену, которую я не могу сломать. Я использую angular + typescript и хочу, чтобы он работал с requirejs. Определено несколько приложений angular, которые загружаются в зависимости от атрибута data-app-name на теле.

Структура папки (упрощенная):

|- Libs
   |- angular
   |- some-plugin
   |- angular-some-plugin // Exposes an angular.module("ngSomePlugin")
   |- require.js
|- Common
   |- Common.ts // Exposes an angular.module("common")
|- App1
   |- Controllers
      |- SomeController.ts
      |- SomeOtherController.ts
   |- Services
      |- SomeService.ts
   |- Main.ts
   |- App.ts
|- App2
   // same as above
|- AppX
   // same as above
|- Index.html
|- Main.ts

Содержание:

Index.html:

// All these attributes are set dynamically server-side
<body id="ng-app-wrapper" data-directory="App1" data-app-name="MyApp">
    <script src="Libs/require.js" data-main="Main"></script>
</body>

Main.ts:

console.log("Step 1: Main.js");

requirejs.config({
    paths: {
        "angular": "Libs/angular/angular",
        "common": "Common/common"
    },
    shim: {
        "angular": {
            exports: "angular"
        }
    }
});

require(["angular"], (angular: angular.IAngularStatic) => {
    angular.element(document).ready(function() {

        var $app = angular.element(document.getElementById("ng-app-wrapper"));
        var directory = $app.data("directory");
        var appName = $app.data("app-name");

        requirejs.config({
            paths: {
                "appMain": directory + "/Main"
            }
        });

        require([
            'common',
            'appMain'
        ], function () {
            console.log("Step 5: App1/Main.js loaded");
            console.log("Step 6: Bootstrapping app: " + appName);
            angular.bootstrap($app, [appName]);
        });
    });
});

Main.ts в App1:

console.log("Step 2: App1/Main.js");

requirejs.config({
    paths: {
        "app": "App1/App",
        "somePlugin": "Libs/some-plugin/some-plugin", // This is an AMD module
        "ngSomePlugin": "Libs/angular-some-plugin/angular-some-plugin"
    },
    shim: {
        "ngSomePlugin": {
            exports: "ngSomePlugin",
            deps: ["somePlugin"]
        }
    }
});

define([
    "app"
], () => {
    console.log("Step 4: App.js loaded");
});

App1/App.ts:

console.log("Step 3: App.js");

import SomeController = require("App1/Controllers/SomeController");
import SomeOtherController = require("App1/Controllers/SomeOtherController");
import SomeService = require("App1/Services/SomeService");

define([
    "angular",
    "ngSomePlugin"
], (angular: angular.IAngularStatic) => {

    // This isn't called, so obviously executed to late
    console.log("Defining angular module MyApp");

    angular.module("MyApp", ["common", "ngSomePlugin"])
        .controller("someCtrl", SomeController.SomeController)
        .controller("someOtherCtrl", SomeOtherController.SomeOtherController)
        .service("someService", SomeService.SomeService)
        ;
});

Это, однако, похоже, сломается с хорошей старой ошибкой: Ошибка при вскрытии: [$ инжектор: nomod] Модуль "MyApp" недоступен! Вы либо ошибочно написали имя модуля, либо забыли загрузить его.

Вопрос 1:

Что я здесь делаю неправильно? Как я могу убедиться, что вызов angular.module() выполняется до того, как я загружу свое приложение?

Это результат работы console.logs, где вы можете видеть, что angular еще не определил модуль angular.module( "MyApp" ), так что это делается до конца, очевидно: Журнал консоли

UPDATE Я могу развернуть вызовы angular в App.ts, поэтому он ничего не требует (кроме импорта вверху). Затем, если я добавлю App в прокладку в App1/Main.ts и заложил там зависимости, похоже, он работает. Это хороший способ решить эту проблему?

UPDATE2 Если я использую require вместо define в App.ts, он создает экземпляр модуля angular, но все же после его попытки загрузить его.

Вопрос 2:

Есть ли способ передать пользовательскую конфигурацию, например имя каталога, где находятся Libs? Я попробовал следующее, которое, похоже, не работало (Main.ts):

requirejs.config({
    paths: {
        "appMain": directory + "/Main"
    },
    config: {
        "appMain": {
            libsPath: "Libs/"
        },
        "app": {
            name: appName
        }
    }
});

App1/Main.ts:

define(["module"], (module) => {
    var libsPath = module.config().libsPath;
    requirejs.config({
        paths: {
            "somePlugin": libsPath + "somePlugin/somePlugin"
            // rest of paths
        }
    });

    define([ // or require([])
        "app"
    ], () => {});
});

App.ts:

define([
    "module"
    // others
], (module) => {
    angular.module(module.config().name, []);
});

Но таким образом, логически, angular.bootstrap() не ждет загрузки App.ts. Поэтому модуль angular еще не определен, и он не может загрузиться. Кажется, что вы не можете сделать вложенное определение, как в App1/Main.ts? Как мне настроить это?

Ответ 1

Вопрос 1

Александр Белецкий написал статью статью о том, как связывать requireJS и Angular вместе (и о том, почему это того стоит). В нем я думаю, что у него есть ответ на ваш первый вопрос: Загрузите Angular из другого модуля, а затем выполните вызов начальной загрузки. Это вынуждает requireJS загружать зависимости для этих модулей перед выполнением какого-либо кода.

Предполагая, что это ваш main.ts

console.log("Step 1: Main.js");

requirejs.config({
    paths: {
        "angular": "Libs/angular/angular",
        "common": "Common/common".
        "mainT1": "t1/main.js"
    },
    shim: {
        "angular": {
            exports: "angular"
        }
    }
});

Добавьте в самый конец вызов следующего модуля

requirejs(["app"], function(app) {
    app.init();
});

Здесь, в основном t1, вы делаете фактическое приложение и загружаете его сразу.

t1/main.ts

define("app-name", ['angular'], function(angular){

    var loader = {};

    loader.load = function(){

         var app = angular.module("app-name",[]);

         return app;
         }

 return loader;

}

Наконец, скажем, что у вас есть файл промежуточного уровня, который он несет вас, чтобы вызвать app.js. Здесь вы можете настроить последовательность, чтобы получить объект, который уже завершил последовательность загрузки Angular. После завершения, THEN вы вызываете загрузку в функции init().

t1/app.js

define("app", function(require){
     var angular = require('angular');
     var appLoader = require('mainT1');

     var app = {}
     app.init = function(){
           var loader = new appLoader();
           loader.load();
           angular.bootstrap(document, ["app-name"]);
     }

}

Ответ 2

В связи с вопросом 1:

В App1/App.ts функция не должна возвращать модуль angular для его ввода.

например.

define([
    "angular",
    "ngSomePlugin"
], (angular: angular.IAngularStatic) => {
    return angular.module("MyApp", ["common", "ngSomePlugin"])
        .controller("someCtrl", SomeController.SomeController)
        .controller("someOtherCtrl", SomeOtherController.SomeOtherController)
        .service("someService", SomeService.SomeService)
        ;
});