Reputation: 2704
The short story is that I'm trying to lever some data model code that's not written for angular in particular into an angular application. This code is written using ES6 import / export syntax for modules, and I'd like to keep using that. So I have something like:
export class DataModel {
//some stuff with promises
}
What I did was create a utility module that exposes the relevant Angular (1.5) services to the ES6 module system thusly:
import angular from 'angular';
export const services = {};
angular.injector(['ng', 'toastr']).invoke([
'$q',
'$http',
'$rootScope',
(
$q,
$http,
$rootScope
) => {
services.$q = $q;
services.$http = $http;
services.$rootScope = $rootScope;
},
]);
Then I can just import the $q library into my DataModel classes and hey presto, everything kind of works - I'm doing promises, and the appropriate scopes should update when the .then methods fire.
The problem is that this doesn't actually work. I'm 90% sure that the reason this doesn't work is that the $rootScope element I get from the angular.injector call isn't a singleton rootscope, it's a fresh new one that gets created just for this context. It does not share any scope linkage with the actual scope on the page (I can confirm this by selecting a DOM element and comparing services.$rootScope to angular.element($0).scope().$root). Therefore, when a promise resolves or a $http returns, I get the data but have the standard symptoms of not notifying a scope digest in the interface (nothing changes until I manually trigger a digest).
All I really want is a copy of the $q, $rootScope and $http services that angular uses live in the active page. Any suggestions are welcome. My next try will be to see if I can grab the relevant services from some .run block where I inject $q et al instead of doing it with the injector. That introduces some problematic timing issues, though, since I need to bootstrap angular, run the run block, and then expose the services to my data model. But the bootstrapping process requires the datamodel. It's a bit circular.
Upvotes: 1
Views: 159
Reputation: 2704
I'm answering this myself for now, but would love to see any other ideas.
I changed the angularServices code to look like:
import angular from 'angular';
import { Rx } from 'rx-lite';
export const servicesLoaded = new Rx.Subject();
export const services = {};
angular.module('app.services', []).run([
'$q',
'$http',
'$rootScope',
(
$q,
$http,
$rootScope
) => {
services.$q = $q;
services.$http = $http;
services.$rootScope = $rootScope;
servicesLoaded.onCompleted();
},
]);
Since I was already using rx-lite anyway. This allows me to do
import { services } from 'angularServices';
services.$http(options) // etc;
whenever I'm working in code that is run after the application bootstrap cycle. For the code that was running prematurely (it was just config stuff that was in a few places, I wrapped it inside the RxJS event thusly:
import { services, servicesLoaded } from '../../common/angularServices';
servicesLoaded.subscribeOnCompleted(() => {
services.$rootScope.$on('$stateChangeSuccess', () => {
//etc
That way I don't try to get in touch with $rootScope or $window before it actually exists, but the $q, $rootScope, and $http I've stashed a reference to in my services
object is actually a real thing, and digests all fire properly.
And now hey presto, while my model layer references $http and $q, they'll be pretty easy to exchange with some other provider of promises and XHRs, making all the work I put into that not bound to angular 1.x. Whee.
Upvotes: 0