-
Notifications
You must be signed in to change notification settings - Fork 34
/
inject-styles.ts
128 lines (120 loc) · 4.29 KB
/
inject-styles.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
import {
APP_INITIALIZER,
ComponentFactoryResolver,
NgModuleRef,
Provider,
RendererFactory2,
ViewEncapsulation
} from '@angular/core';
import { Router } from '@angular/router';
import { stylesFromModule } from '@polymer/polymer/lib/utils/style-gather';
import { whenSet } from '@codebakery/origami/util';
import { getStyleModulesFor } from './include-styles';
import { styleToEmulatedEncapsulation } from './style-to-emulated-encapsulation';
import { getTypeFor, scanComponentFactoryResolver } from './type-selectors';
/**
* Provider that ensures `injectIncludeStyles()` will run on application
* startup before components are created.
*/
export const INJECT_STYLES_PROVIDER: Provider = {
provide: APP_INITIALIZER,
multi: true,
useFactory: injectIncludeStyles,
deps: [NgModuleRef]
};
/**
* Provider that ensures `injectIncludeStyles()` will run on application
* startup before components are created. This provider does _not_ require
* @angular/router.
*/
export const INJECT_STYLES_NO_ROUTER_PROVIDER: Provider = {
provide: APP_INITIALIZER,
multi: true,
useFactory: injectIncludeStylesNoRouter,
deps: [NgModuleRef]
};
/**
* Returns a callback that, when invoked, will use the provided `NgModuleRef`
* to patch the renderer factory and scan the component factory resolver in
* order to enable injecting Polymer style modules for components decorated with
* `@IncludeStyles()`.
*
* This function will additionally listen to any lazy-loaded modules from
* Angular's router and scan component factory resolvers that are added after
* the app has initialized.
*
* @param ngModule the root `NgModule` reference
* @returns a callback that will begin the injection process
*/
export function injectIncludeStyles(ngModule: NgModuleRef<any>): () => void {
const injectStyles = injectIncludeStylesNoRouter(ngModule);
return () => {
injectStyles();
const router = <Router>ngModule.injector.get(Router);
router.events.subscribe(e => {
if ('route' in e && !(<any>e.route)._loadedConfig) {
whenSet(<any>e.route, '_loadedConfig', undefined, config => {
scanComponentFactoryResolver(
config.module.injector.get(ComponentFactoryResolver)
);
});
}
});
};
}
/**
* Returns a callback that, when invoked, will use the provided `NgModuleRef`
* to patch the renderer factory and scan the component factory resolver in
* order to enable injecting Polymer style modules for components decorated with
* `@IncludeStyles()`.
*
* @param ngModule the root `NgModule` reference
* @returns a callback that will begin the injection process
*/
export function injectIncludeStylesNoRouter(
ngModule: NgModuleRef<any>
): () => void {
return () => {
patchRendererFactory(ngModule.injector.get(RendererFactory2));
scanComponentFactoryResolver(
ngModule.injector.get(ComponentFactoryResolver)
);
};
}
const INJECTED_SELECTORS: string[] = [];
/**
* Patches a `RendererFactory2` to overwrite `createRenderer()` and add styles
* imported from Polymer style modules according to `@IncludeStyles()`
* decorators to the `RendererType2` data for the element.
*
* If the element type using emulated view encapsulation, the styles imported
* will be converted to preserve encapsulation.
*
* @param factory the renderer factory to patch
*/
export function patchRendererFactory(factory: RendererFactory2) {
const $createRenderer = factory.createRenderer;
factory.createRenderer = function(element, type) {
const selector = element && element.localName;
if (selector && type && INJECTED_SELECTORS.indexOf(selector) === -1) {
const styleModules = getStyleModulesFor(getTypeFor(selector));
let styles = styleModules.map(styleModule => {
const styleElements = stylesFromModule(styleModule);
return styleElements.map(e => e.innerText).join('\n');
});
switch (type.encapsulation) {
case ViewEncapsulation.Emulated:
default:
styles = styles.map(style => styleToEmulatedEncapsulation(style));
break;
case ViewEncapsulation.None:
case ViewEncapsulation.Native:
case ViewEncapsulation.ShadowDom:
break;
}
type.styles.push(...styles);
INJECTED_SELECTORS.push(selector);
}
return $createRenderer.apply(this, <any>arguments);
};
}