问题
I am trying to get my angular application work in live mode as well as in a prototype mode just by overriding the services. As a part of this when the prototype mode is turned on in the config, i halt the bootstrap process, load mock services (js) files and resume bootstrapping.
Here is a simplified list of source code for preparing the demo:-
App.js
Just my app, which for simulation calls the service and display the result. It required StubApp
as well whose provide this is going to use to override the services
var app = angular.module('app', ['StubsApp'])
.run([ '$rootScope', 'DataService', function($scope, DataService){
DataService.getData().then(function(data){
$scope.name = data;
});
}]);
DataService.js
Just a simple service registered with the app.
function DataService($q){
this.getData = function(){
return $q.when('I am Real!!');
}
}
DataService.$inject = ['$q'];
angular.module('app').service('DataService',DataService);
Driver.js
Just the config registration which will set up mocking.
angular.module('app').config(['$provide', 'stubServiceProvider', 'AppConfig', function($provide, stubProvider, AppConfig){
if(AppConfig.StubEnabled){
stubProvider.loadStubsInModule('plunker');
}
}]);
StubProvider.js
This exposes an interface similar to angular.module
to register stub services. it also looks for stubs.json
which has the list of mock services which is loaded by halting the bootstrap. It also exposes a provide which an App can use to set up overload of existing services with the ones in the stubs.json
var Stubs = {},
modules = [];
function module(moduleName) {
return {
mock: function (func) {
modules.push(func);
}, get: function () {
return modules;
}
};
}
Stubs.module = module;
loadStubs();
function loadStubs() {
window.name = "NG_DEFER_BOOTSTRAP!";
var injector = angular.injector(['ng']);
var $q = injector.get('$q');
var $http = injector.get('$http');
var scripts = [];
$http.get('stubs.json').then(function (result) {
scripts = result.data.map(function (src) {
var script = document.createElement('script');
script.src = src;
script.async = true;
document.head.appendChild(script);
var defered = $q.defer();
script.onload = function () {
defered.resolve();
};
return defered.promise;
});
$q.all(scripts).finally(function () {
angular.element().ready(function () {
angular.resumeBootstrap();
});
});
});
}
//This is the provider which actually will do the overriding
angular.module('StubsApp', []).provider('stubService', function ($provide) {
...... //Code in plunker
});
DataService Mock.js
This is the just a mock which actually uses Stubs interface to register the mock Stubs.module('app').mock(MockService)
and the ctor has a property stubFor="serviceName"
which tells which service this actually mocks
function MockService($q, $log){
this.getData = function(){
return $q.when('I am Mock!!');
}
}
MockService.$inject = ['$q', '$log'];
MockService.stubFor="DataService";
Stubs.module('app').mock(MockService);
stubs.json
Just a simple json file that specifies the mocks
["DataServiceMock.js"]
index.html
<script src="app.js"></script>
<script src="DataService.js"></script>
<script src="Driver.js"></script>
<script src="stubprovider.js"></script>
This works fine. Now the issue is that when i move Driver.js
file before the service registration i.e DataService.js
it won't mock anymore. Specific portion of code that performs overriding in the "StubProvider.js" is
Stubs.module(moduleName).get().forEach(function (mod) {
var serviceName = mod.stubFor;
var ctor = mod;
if (serviceName) {
$provide.service(serviceName, ctor);
}
});
Here is a Demo Plnkr If you comment out the line in the Driver.js
you can see the output will be from the real service other wise it will be from the mock service. And to replicate the issue in the index.html mode Driver.js
before DataService.js
it won't override DataService with MockDataservice.
Why is it that the order of config registration matter, config phase is supposed to run before the service instantiation anyways correct?
Is there a better pattern to ensure all the scripts are loaded before resuming the bootstrap process rather than using the deferred pattern.
回答1:
Use the createElement
and appendChild
DOM methods, the src
and onload
attributes of the script
element, and the bootstrap
, element
and injector
methods of AngularJS:
/* Create script element */
var script = document.createElement('script');
/* Set src */
script.src = "https://ajax.googleapis.com/ajax/libs/angularjs/1.3.1/angular.min.js";
/* Append to head */
document.getElementsByTagName("head")[0].appendChild(script);
function dothis()
{
//local datastore
this.mvvm = {};
//template string
var html = "<div>ID: {{$id}}</div>".replace("|",'"',"g");
//template object
var template = angular.element(html);
//template transformer
var compiler = angular.injector(["ng"]).get("$compile");
//template result
var linker = compiler(template);
//scope object
var scope = angular.injector(["ng"]).get("$rootScope");
//scope binding
var result = linker(scope)[0];
/* Append result to body */
document.body.appendChild(result);
/* Render */
angular.bootstrap(document, ['ng']);
}
script.onload = dothis;
来源:https://stackoverflow.com/questions/25537396/issue-with-manual-bootstrapping-and-overriding-angular-services-in-config