2

I'm struggling to get a custom provider to work with dependencies injected into it. I'm following this blog and here is my latest version of the provider in my attempts to get it working.

define([
    'angular',
    'ngRoute',
    'require'
], function(angular) {
    return angular.module('pluggableViews', ['ngRoute'])
        .provider('$pluggableViews', function() {
                var providers = {};
                var $injector = angular.injector(['ng']);
                this.views = [];

                this.registerModule = function(moduleName) {
                    console.log(moduleName);
                    var module = angular.module(moduleName);

                    if (module.requires) {
                        for (var i = 0; i < module.requires.length; i++) {
                            this.registerModule(module.requires[i]);
                        }
                    }

                    angular.forEach(module._invokeQueue, function(invokeArgs) {
                        var provider = providers[invokeArgs[0]];
                        provider[invokeArgs[1]].apply(provider, invokeArgs[2]);
                    });
                    angular.forEach(module._configBlocks, function(fn) {
                        $injector.invoke(fn);
                    });
                    angular.forEach(module._runBlocks, function(fn) {
                        $injector.invoke(fn);
                    });
                };

                this.toTitleCase = function(str) {
                    return str.replace(/\w\S*/g, function(txt) {
                        return txt.charAt(0).toUpperCase() + txt.substr(1).toLowerCase();
                    });
                };

                this.registerView = function(viewConfig) {

                    if (!viewConfig.viewUrl) {
                        viewConfig.viewUrl = '/' + viewConfig.ID;
                    }
                    if (!viewConfig.templateUrl) {
                        viewConfig.templateUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.html';
                    }
                    if (!viewConfig.controller) {
                        viewConfig.controller = this.toTitleCase(viewConfig.ID) + 'Controller';
                    }
                    if (!viewConfig.navigationText) {
                        viewConfig.navigationText = this.toTitleCase(viewConfig.ID);
                    }
                    if (!viewConfig.requirejsName) {
                        viewConfig.requirejsName = viewConfig.ID;
                    }
                    if (!viewConfig.moduleName) {
                        viewConfig.moduleName = viewConfig.ID;
                    }
                    if (!viewConfig.cssId) {
                        viewConfig.cssId = viewConfig.ID + "-css";
                    }
                    if (!viewConfig.cssUrl) {
                        viewConfig.cssUrl = 'views/' + viewConfig.ID + '/' + viewConfig.ID + '.css';
                    }

                    this.views.push(viewConfig);

                    $route.when(viewConfig.viewUrl, {
                        templateUrl: viewConfig.templateUrl,
                        controller: viewConfig.controller,
                        resolve: {
                            resolver: ['$q', '$timeout', function($q, $timeout) {

                                var deferred = $q.defer();
                                if (angular.element("#" + viewConfig.cssId).length === 0) {
                                    var link = document.createElement('link');
                                    link.id = viewConfig.cssId;
                                    link.rel = "stylesheet";
                                    link.type = "text/css";
                                    link.href = viewConfig.cssUrl;
                                    angular.element('head').append(link);
                                }
                                if (viewConfig.requirejsConfig) {
                                    require.config(viewConfig.requirejsConfig);
                                }
                                require([viewConfig.requirejsName], function() {
                                    this.registerModule(viewConfig.moduleName);
                                    $timeout(function() {
                                        deferred.resolve();
                                    });
                                });
                                return deferred.promise;
                            }]
                        }
                    });

                };

                this.$get = [
                    '$controller',
                    '$compile',
                    '$filter',
                    // '$provide',
                    // '$injector',
                    '$route',
                    function(
                        $controller,
                        $compile,
                        $filter,
                        //$provide,
                        //$injector,
                        $route
                    ) {
                        providers.$controller = $controller;
                        providers.$compile = $compile;
                        providers.$filter = $filter;
                        providers.$route = $route;
                    }];
            });
});

And here is configuring the provider:

define([
    'angular',
    'ngRoute',
    'views/nav/nav',
    'scripts/providers/pluggableViews'
], function (angular) {
    var app = angular.module('app', ['ngRoute', 'pluggableViews', 'app.nav']);

    app.directive('navbar', function () {
        return {
            restrict: 'E',
            templateUrl: '../views/nav/nav.html'
        };
    });


    app.config([
        '$routeProvider', 
        '$locationProvider', 
        '$pluggableViewsProvider'
    ], function($routeProvider, $locationProvider, $pluggableViewsProvider){
        $pluggableViewsProvider.registerView({
            ID: 'home',
            moduleName: 'app.home',
            requirejsConfig: {paths: {'home': 'views/home/home'}},
            viewUrl: '/'
        });
    }]);

    return app;
});

So far I've determined that the original article was incorrectly injecting dependencies into the .provider() instead of the $get function. I've attempted to correct this but I am still getting a "Error: [$injector:modulerr]" for my "app" module when I inject the provider. If I remove the provider from my "app" config the error goes away. So I have determined it is in fact my provider that is in error.

Update

After more debugging and isolating code. I've updated the code above to reflect my new discoveries that injected providers should leave off the "Provider" at the end of their name. I've also discovered that $injector and $provide services are causing errors. Can you not inject these services into a provider? It seems right now things are erroring when my app tries to call the registerView function. I believe the $route.resolve isn't resolving correctly.

4

2 回答 2

2

最初的问题是由于每个应用程序实例有两个注入器,分别属于“配置”和“运行”阶段。一种是服务提供者(由 定义provider),其他服务提供者可以在那里注入(即$controllerProvider)。另一种是服务实例(它的工厂函数由factoryor定义),只有服务实例可以注入那里(即)。provider$get$controller

对于服务实例中的惰性控制器注册,它将是

app.provider('...', function($controllerProvider) {
  this.$get = function ($controller) {
    $controllerProvider.register('LazyController', ...);
    var lazyControllerInstance = $controller('LazyController', ...);
  };
})

可以执行类似的技巧来懒惰地定义新的路由$routeProvider和定义新的指​​令$compileProvider

另一方面,您试图解决的问题在 Angular 中是不可能的。

一旦应用程序启动并开始配置阶段,就无法定义新的 Angular 模块(技术上它们可以,但它们不能在此应用程序实例中使用)。必须在应用程序中使用的每个模块都必须在定义模块时加载。这些可以是虚拟模块,但它们必须存在才能使用,例如

angular.module('dummy', []);

angular.module('app', ['dummy'])
  .config(($provide, $controllerProvider) => {
    // these ones are necessary to register new items after config phase
    $provide.value('$controllerProvider', $controllerProvider);
    $provide.value('$provide', $provide);
  })
  .run(() => {
    require(['dummy'], ...);
  });

// dummy.js

angular.module('dummy').run(($provide, $controllerProvider) => {
  $provide.factory('lazy', ...)
  $controllerProvider.register('LazyController', ...);
});
于 2016-01-18T14:25:35.733 回答
0

在尝试解决这个问题进行更多研究后,我发现了一个可以为我解决动态注册问题的库。AngularAMD与 AMDefine/RequireJS 一起使用以启用角度模块的动态注册。它解决了我试图通过这篇文章完成的工作(我的代码更少!)。因为我花了很长时间才弄清楚这一切,所以我继续为我试图用我的网络应用程序做的所有基础工作创建了一个模板,这样其他人就可以看到我最终是如何做到的。你可以在这里找到它。

更新

@estus 对 AngularAMD 提出了一些合理的担忧。我将不得不进一步调查。

于 2016-01-20T20:49:26.687 回答