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)
;
});