I seem to have hit a wall I am not able to break down. I am using angular + typescript, and want to make it work with requirejs. There are multiple angular apps defined that are loaded dependent on a data-app-name attribute on the body.
Folder structure (simplified):
|- 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
Contents:
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 in 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)
;
});
This however seems to break, with the good old error: Uncaught Error: [$injector:nomod] Module 'MyApp' is not available! You either misspelled the module name or forgot to load it.
Question 1:
What am I doing wrong here? How can I make sure the angular.module()
call is done before I am bootstrapping my app?
This is the output of the console.logs, where you can see angular hasn't yet defined the module angular.module("MyApp"), so this is done to late obviously:
UPDATE I can unwrap the angular calls in App.ts, so it doesn't require anything (except for the imports at the top). Then if I add App to the shim in App1/Main.ts, and lay the dependencies there it seems to work. Is this a good way to solve this?
UPDATE2 If I use require instead of define in App.ts, it does instantiate the angular module, but still after it tries to bootstrap it.
Question 2:
Is there any way to pass down custom configuration, for example the directory name where the Libs are? I tried the following which did not seem to work (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, []);
});
But this way, logically, the angular.bootstrap() does not wait for App.ts to be loaded. Therefore the angular module isn't defined yet and it fails to bootstrap. It seems that you cannot do a nested define as in App1/Main.ts? How should I configure this?
require.config()
calls will work as expected. (Side-question: do they?) But since thedata-directory
/data-app-name
are generated server-side, I'd suggest you generate therequire.config()
call dynamically too. You may even want to dynamically, server-side include the appropriate fragment directly into the Index.html. From there, you will need to tweak the paths in the AMD modules. And a sidenote: why aren't you using Typescript's native support for AMD, i.e. theimport Foo = require('Foo')
syntax? – Nikos Paraskevopoulosimport
stuff on angular-app level (in App.ts and all other angular components). Is it better to use the import syntax everywhere if I'm using typescript? i don't think it will work on non-AMD third party libraries? – devqonrequire.config()
seems to work :) – devqon