Skip to content

Commit d6595eb

Browse files
vikermanvicb
authored andcommitted
feat(platform-server): use EventManagerPlugin on the server (#24132)
Previously event handlers on the server were setup directly. This change makes it so that the event registration on the server go through EventManagerPlugin just like on client. This allows us to add custom event registration handlers on the server which allows us to hook up preboot event handlers cleanly. PR Close #24132
1 parent 5b25c07 commit d6595eb

File tree

4 files changed

+65
-20
lines changed

4 files changed

+65
-20
lines changed

packages/platform-browser/src/dom/events/dom_events.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ export class DomEventsPlugin extends EventManagerPlugin {
110110
}
111111

112112
private patchEvent() {
113-
if (!Event || !Event.prototype) {
113+
if (typeof Event === 'undefined' || !Event || !Event.prototype) {
114114
return;
115115
}
116116
if ((Event.prototype as any)[stopMethodSymbol]) {

packages/platform-server/src/server.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,15 @@ import {PlatformLocation, ɵPLATFORM_SERVER_ID as PLATFORM_SERVER_ID} from '@ang
1111
import {HttpClientModule} from '@angular/common/http';
1212
import {Injectable, InjectionToken, Injector, NgModule, NgZone, Optional, PLATFORM_ID, PLATFORM_INITIALIZER, PlatformRef, Provider, RendererFactory2, RootRenderer, StaticProvider, Testability, createPlatformFactory, isDevMode, platformCore, ɵALLOW_MULTIPLE_PLATFORMS as ALLOW_MULTIPLE_PLATFORMS} from '@angular/core';
1313
import {HttpModule} from '@angular/http';
14-
import {BrowserModule, DOCUMENT, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
14+
import {BrowserModule, DOCUMENT, EVENT_MANAGER_PLUGINS, ɵSharedStylesHost as SharedStylesHost, ɵTRANSITION_ID, ɵgetDOM as getDOM} from '@angular/platform-browser';
1515
import {ɵplatformCoreDynamic as platformCoreDynamic} from '@angular/platform-browser-dynamic';
1616
import {NoopAnimationsModule, ɵAnimationRendererFactory} from '@angular/platform-browser/animations';
1717

1818
import {DominoAdapter, parseDocument} from './domino_adapter';
1919
import {SERVER_HTTP_PROVIDERS} from './http';
2020
import {ServerPlatformLocation} from './location';
2121
import {PlatformState} from './platform_state';
22+
import {ServerEventManagerPlugin} from './server_events';
2223
import {ServerRendererFactory2} from './server_renderer';
2324
import {ServerStylesHost} from './styles_host';
2425
import {INITIAL_CONFIG, PlatformConfig} from './tokens';
@@ -58,6 +59,7 @@ export const SERVER_RENDER_PROVIDERS: Provider[] = [
5859
},
5960
ServerStylesHost,
6061
{provide: SharedStylesHost, useExisting: ServerStylesHost},
62+
{provide: EVENT_MANAGER_PLUGINS, multi: true, useClass: ServerEventManagerPlugin},
6163
];
6264

6365
/**
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
/**
2+
* @license
3+
* Copyright Google Inc. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by an MIT-style license that can be
6+
* found in the LICENSE file at https://angular.io/license
7+
*/
8+
9+
import {Inject, Injectable} from '@angular/core';
10+
import {DOCUMENT, ɵgetDOM as getDOM} from '@angular/platform-browser';
11+
12+
@Injectable()
13+
export class ServerEventManagerPlugin /* extends EventManagerPlugin which is private */ {
14+
constructor(@Inject(DOCUMENT) private doc: any) {}
15+
16+
// Handle all events on the server.
17+
supports(eventName: string) { return true; }
18+
19+
addEventListener(element: HTMLElement, eventName: string, handler: Function): Function {
20+
return getDOM().onAndCancel(element, eventName, handler);
21+
}
22+
23+
addGlobalEventListener(element: string, eventName: string, handler: Function): Function {
24+
const target: HTMLElement = getDOM().getGlobalEventTarget(this.doc, element);
25+
if (!target) {
26+
throw new Error(`Unsupported event target ${target} for event ${eventName}`);
27+
}
28+
return this.addEventListener(target, eventName, handler);
29+
}
30+
}

packages/platform-server/src/server_renderer.ts

Lines changed: 31 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88

99
import {DomElementSchemaRegistry} from '@angular/compiler';
1010
import {APP_ID, Inject, Injectable, NgZone, RenderComponentType, Renderer, Renderer2, RendererFactory2, RendererStyleFlags2, RendererType2, RootRenderer, ViewEncapsulation, ɵstringify as stringify} from '@angular/core';
11-
import {DOCUMENT, ɵNAMESPACE_URIS as NAMESPACE_URIS, ɵSharedStylesHost as SharedStylesHost, ɵflattenStyles as flattenStyles, ɵgetDOM as getDOM, ɵshimContentAttribute as shimContentAttribute, ɵshimHostAttribute as shimHostAttribute} from '@angular/platform-browser';
11+
import {DOCUMENT, EventManager, ɵNAMESPACE_URIS as NAMESPACE_URIS, ɵSharedStylesHost as SharedStylesHost, ɵflattenStyles as flattenStyles, ɵgetDOM as getDOM, ɵshimContentAttribute as shimContentAttribute, ɵshimHostAttribute as shimHostAttribute} from '@angular/platform-browser';
1212

1313
const EMPTY_ARRAY: any[] = [];
1414

@@ -19,9 +19,9 @@ export class ServerRendererFactory2 implements RendererFactory2 {
1919
private schema = new DomElementSchemaRegistry();
2020

2121
constructor(
22-
private ngZone: NgZone, @Inject(DOCUMENT) private document: any,
23-
private sharedStylesHost: SharedStylesHost) {
24-
this.defaultRenderer = new DefaultServerRenderer2(document, ngZone, this.schema);
22+
private eventManager: EventManager, private ngZone: NgZone,
23+
@Inject(DOCUMENT) private document: any, private sharedStylesHost: SharedStylesHost) {
24+
this.defaultRenderer = new DefaultServerRenderer2(eventManager, document, ngZone, this.schema);
2525
}
2626

2727
createRenderer(element: any, type: RendererType2|null): Renderer2 {
@@ -34,7 +34,8 @@ export class ServerRendererFactory2 implements RendererFactory2 {
3434
let renderer = this.rendererByCompId.get(type.id);
3535
if (!renderer) {
3636
renderer = new EmulatedEncapsulationServerRenderer2(
37-
this.document, this.ngZone, this.sharedStylesHost, this.schema, type);
37+
this.eventManager, this.document, this.ngZone, this.sharedStylesHost, this.schema,
38+
type);
3839
this.rendererByCompId.set(type.id, renderer);
3940
}
4041
(<EmulatedEncapsulationServerRenderer2>renderer).applyToHost(element);
@@ -61,18 +62,19 @@ class DefaultServerRenderer2 implements Renderer2 {
6162
data: {[key: string]: any} = Object.create(null);
6263

6364
constructor(
64-
private document: any, private ngZone: NgZone, private schema: DomElementSchemaRegistry) {}
65+
private eventManager: EventManager, protected document: any, private ngZone: NgZone,
66+
private schema: DomElementSchemaRegistry) {}
6567

6668
destroy(): void {}
6769

6870
destroyNode: null;
6971

7072
createElement(name: string, namespace?: string, debugInfo?: any): any {
7173
if (namespace) {
72-
return getDOM().createElementNS(NAMESPACE_URIS[namespace], name);
74+
return getDOM().createElementNS(NAMESPACE_URIS[namespace], name, this.document);
7375
}
7476

75-
return getDOM().createElement(name);
77+
return getDOM().createElement(name, this.document);
7678
}
7779

7880
createComment(value: string, debugInfo?: any): any { return getDOM().createComment(value); }
@@ -166,14 +168,25 @@ class DefaultServerRenderer2 implements Renderer2 {
166168
listen(
167169
target: 'document'|'window'|'body'|any, eventName: string,
168170
callback: (event: any) => boolean): () => void {
169-
// Note: We are not using the EventsPlugin here as this is not needed
170-
// to run our tests.
171171
checkNoSyntheticProp(eventName, 'listener');
172-
const el =
173-
typeof target === 'string' ? getDOM().getGlobalEventTarget(this.document, target) : target;
174-
const outsideHandler = (event: any) => this.ngZone.runGuarded(() => callback(event));
175-
return this.ngZone.runOutsideAngular(
176-
() => getDOM().onAndCancel(el, eventName, outsideHandler) as any);
172+
if (typeof target === 'string') {
173+
return <() => void>this.eventManager.addGlobalEventListener(
174+
target, eventName, this.decoratePreventDefault(callback));
175+
}
176+
return <() => void>this.eventManager.addEventListener(
177+
target, eventName, this.decoratePreventDefault(callback)) as() => void;
178+
}
179+
180+
private decoratePreventDefault(eventHandler: Function): Function {
181+
return (event: any) => {
182+
// Run the event handler inside the ngZone because event handlers are not patched
183+
// by Zone on the server. This is required only for tests.
184+
const allowDefaultBehavior = this.ngZone.runGuarded(() => eventHandler(event));
185+
if (allowDefaultBehavior === false) {
186+
event.preventDefault();
187+
event.returnValue = false;
188+
}
189+
};
177190
}
178191
}
179192

@@ -190,9 +203,9 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 {
190203
private hostAttr: string;
191204

192205
constructor(
193-
document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost,
206+
eventManager: EventManager, document: any, ngZone: NgZone, sharedStylesHost: SharedStylesHost,
194207
schema: DomElementSchemaRegistry, private component: RendererType2) {
195-
super(document, ngZone, schema);
208+
super(eventManager, document, ngZone, schema);
196209
const styles = flattenStyles(component.id, component.styles, []);
197210
sharedStylesHost.addStyles(styles);
198211

@@ -203,7 +216,7 @@ class EmulatedEncapsulationServerRenderer2 extends DefaultServerRenderer2 {
203216
applyToHost(element: any) { super.setAttribute(element, this.hostAttr, ''); }
204217

205218
createElement(parent: any, name: string): Element {
206-
const el = super.createElement(parent, name);
219+
const el = super.createElement(parent, name, this.document);
207220
super.setAttribute(el, this.contentAttr, '');
208221
return el;
209222
}

0 commit comments

Comments
 (0)