UNPKG

3.77 kBJavaScriptView Raw
1import _ from 'lodash';
2import Vue from 'vue';
3
4
5let vue;
6let lastDigestRequest = 0, digestInProgress = false;
7const bareDigest = function() {
8 if (vue.digestRequest > lastDigestRequest) return;
9 vue.digestRequest = lastDigestRequest + 1;
10};
11
12const angularProxy = {
13 active: typeof window !== 'undefined' && window.angular
14};
15
16if (angularProxy.active) {
17 initAngular();
18} else {
19 _.forEach(['digest', 'watch', 'defineModule', 'debounceDigest'], method => {
20 angularProxy[method] = _.noop;
21 });
22}
23
24function initAngular() {
25 const module = window.angular.module('firetruss', []);
26 angularProxy.digest = bareDigest;
27 angularProxy.watch = function() {throw new Error('Angular watch proxy not yet initialized');};
28 angularProxy.defineModule = function(Truss) {
29 module.constant('Truss', Truss);
30 };
31 angularProxy.debounceDigest = function(wait) {
32 if (wait) {
33 const debouncedDigest = _.debounce(bareDigest, wait);
34 angularProxy.digest = function() {
35 if (vue.digestRequest > lastDigestRequest) return;
36 if (digestInProgress) bareDigest(); else debouncedDigest();
37 };
38 } else {
39 angularProxy.digest = bareDigest;
40 }
41 };
42
43 module.config(['$provide', function($provide) {
44 $provide.decorator('$rootScope', ['$delegate', '$exceptionHandler',
45 function($delegate, $exceptionHandler) {
46 const rootScope = $delegate;
47 angularProxy.watch = rootScope.$watch.bind(rootScope);
48 const proto = Object.getPrototypeOf(rootScope);
49 const angularDigest = proto.$digest;
50 proto.$digest = bareDigest;
51 proto.$digest.original = angularDigest;
52 vue = new Vue({data: {digestRequest: 0}});
53 vue.$watch(() => vue.digestRequest, () => {
54 if (vue.digestRequest > lastDigestRequest) {
55 // Make sure we execute the digest outside the Vue task queue, because otherwise if the
56 // client replaced Promise with angular.$q all Truss.nextTick().then() functions will be
57 // executed inside the Angular digest and hence inside the Vue task queue. But
58 // Truss.nextTick() is used precisely to avoid that. Note that it's OK to use
59 // Vue.nextTick() here because even though it will schedule a flush via Promise.then()
60 // it only uses the native Promise, before it could've been monkey-patched by the app.
61 Vue.nextTick(() => {
62 if (vue.digestRequest <= lastDigestRequest) return;
63 digestInProgress = true;
64 rootScope.$digest.original.call(rootScope);
65 lastDigestRequest = vue.digestRequest = vue.digestRequest + 1;
66 });
67 } else {
68 digestInProgress = false;
69 }
70 });
71 const watcher = _.last(vue._watchers || vue._scope.effects);
72 watcher.id = Infinity; // make sure watcher is scheduled last
73 patchRenderWatcherGet(Object.getPrototypeOf(watcher));
74 return rootScope;
75 }
76 ]);
77 }]);
78}
79
80// This is a kludge that catches errors that get through render watchers and end up killing the
81// entire Vue event loop (e.g., errors raised in transition callbacks). The state of the DOM may
82// not be consistent after such an error is caught, but the global error handler should stop the
83// world anyway. May be related to https://github.com/vuejs/vue/issues/7653.
84function patchRenderWatcherGet(prototype) {
85 const originalGet = prototype.get;
86 prototype.get = function get() {
87 try {
88 return originalGet.call(this);
89 } catch (e) {
90 if (this.vm._watcher === this && Vue.config.errorHandler) {
91 Vue.config.errorHandler(e, this.vm, 'uncaught render error');
92 } else {
93 throw e;
94 }
95 }
96 };
97}
98
99export default angularProxy;