0

I have an application which has an app object which does the start routine and stores useful things like app.state and app.user. However I am trying to access this app instance without passing this from the app instance all the way around my large codebase.

Strangely I work on other projects which include app in the same way as in something.js and it works but I can't see why.

index.html

<!DOCTYPE html>
<html>
<head>
    <title>Cannot require app in another file</title>
</head>
<body>
    <script data-main="config" src="require.js"></script>
</body>
</html>

config.js

requirejs.config({
    deps: ['app']
});

app.js

define([
    'something'
], function(Something) {
    'use strict';

    var App = function() {
        this.name = 'My app';
    };

    return new App();
});

something.js

define([
    'require',
    'app'
], function (require, app) {
    'use strict';

    var SomeModule = function() {
        app = require('app'); // EXCEPTION
        console.log('App:', app);
    };

    return new SomeModule();
});

When loading this requirejs exception is throw because of the require in SomeModule:

Uncaught Error: Module name "app" has not been loaded yet for context: _

Demo of above (see console for error): http://dominictobias.com/circulardep/

Dominic
  • 62,658
  • 20
  • 139
  • 163

1 Answers1

1

It's not clear to me why you need to have a circular dependency. As stated in the documentation for RequireJS:

Circular dependencies are rare, and usually a sign that you might want to rethink the design.

This being said, if you do need the circular dependency, the issue with your code is that require('app') is called too early. It cannot be called until after the module something has returned its value. Right now, it is called before the value is returned. If you look at the code given as example in the documentation:

define(["require", "a"],
    function(require, a) {
        //"a" in this case will be null if a also asked for b,
        //a circular dependency.
        return function(title) {
            return require("a").doSomething();
        }
    }
);

you see that the module returns a function which then would be called by the code that required the module, which happens after this module has returned its value.

So how do you fix this? What you could do is have the class you return call a function that fetches module app whenever needed. So:

define([
    'require',
    'app'
], function (require) {
    'use strict';

    var app_;
    function fetch_app() {
        if (app_ === undefined)
            app_ = require("app");
        return app_;
    }

    var SomeModule = function() {
        // ...
    };

    SomeModule.prototype.doSomethingWithApp = function () {
        var app = get_app();
        app.whatever();
    };

    return new SomeModule();
});

I've removed app from the list of arguments and store the value of the app module in app_ because doing it this way provides for early detection of a missing call to get_app() in any method of SomeModule. If app is made a parameter of the module's factory function then using app inside a method without calling get_app() first would be detected only if it so happened that no other method that calls get_app() was called first. (Of course, I could type app_ and face the same problem as the one I aim to prevent. It's a matter of respective likelihoods: I'd be very likely to forget to call get_app() everywhere it is needed because I don't usually write code with circular dependencies. However, I'd be unlikely to type app_ for app because I don't usually put _ at the end of my variable names.)

Louis
  • 146,715
  • 28
  • 274
  • 320
  • Thanks for the detailed response. I was thinking about what you/the docs say about circular dep and I think I have just taken this bad practice from another app without thinking enough about it - you are right I don't need to make this circular loop for the sake of accessing some attached objects I could just require in where needed. – Dominic May 17 '14 at 23:43
  • Yeah, getting rid of the circular dependency will make things much simpler. – Louis May 17 '14 at 23:48
  • To summarise for readers: you basically can't require in app for `new`'ed dependencies during the initialisation routine – Dominic May 19 '14 at 10:15