Skip to content
This repository was archived by the owner on Mar 20, 2024. It is now read-only.

Commit e4dc6e2

Browse files
mgolCaerusKaru
authored andcommitted
fix: make Preboot work with ngUpgrade
When AngularJS is used in an Angular app via ngUpgrade and Preboot is added with buffering enabled, by default AngularJS will compile both app roots (Angular would compile the first one only), leading - in turn - to Angular's AppComponent being rendered twice, in both roots. That breaks the whole Preboot functionality. Adding the ng-non-bindable attribute to the server-rendered node works around this difference in behavior. README has a new section on how to manually trigger replaying events for setups without bootstrap components, e.g. the canonical setup for ngUpgrade. Fixes #67
1 parent b031a18 commit e4dc6e2

File tree

4 files changed

+76
-0
lines changed

4 files changed

+76
-0
lines changed

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,56 @@ var eventSelectors = [
148148
];
149149
```
150150

151+
#### Using with manual bootstrap (e.g. with ngUpgrade)
152+
153+
Preboot registers its reply code at the `APP_BOOTSTRAP_LISTENER` token which is called by Angular for every component that is bootstrapped. If you don't have the `bootstrap` property defined in your `AppModule`'s `NgModule` but you instead use the `ngDoBootrap` method (which is done e.g. when using ngUpgrade) this code will not run at all.
154+
155+
To make Preboot work correctly in such a case you need to specify `noReplay: true` in the Preboot options and replay the events yourself. That is, import `PrebootModule` like this:
156+
157+
```typescript
158+
PrebootModule.withConfig({
159+
appRoot: 'app-root',
160+
noReplay: true,
161+
})
162+
```
163+
164+
and replay events in `AppComponent` like this:
165+
166+
```typescript
167+
import { isPlatformBrowser } from '@angular/common';
168+
import { Component, OnInit, PLATFORM_ID, Inject, ApplicationRef } from '@angular/core';
169+
import { Router } from '@angular/router';
170+
import { filter, take } from 'rxjs/operators';
171+
import { EventReplayer } from 'preboot';
172+
173+
@Component({
174+
selector: 'app-root',
175+
templateUrl: './app.component.html',
176+
styleUrls: ['./app.component.scss'],
177+
})
178+
export class AppComponent implements OnInit {
179+
constructor(
180+
private router: Router,
181+
@Inject(PLATFORM_ID) private platformId: string,
182+
private appRef: ApplicationRef,
183+
private replayer: EventReplayer,
184+
) {}
185+
186+
ngOnInit() {
187+
this.router.initialNavigation();
188+
if (isPlatformBrowser(this.platformId)) {
189+
this.appRef.isStable
190+
.pipe(
191+
filter(stable => stable),
192+
take(1),
193+
).subscribe(() => {
194+
this.replayer.replayAll();
195+
});
196+
}
197+
}
198+
}
199+
```
200+
151201
#### PrebootComplete
152202

153203
When you are manually replaying events, you often will want to know when Preboot

src/lib/api/event.recorder.spec.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,16 @@ describe('UNIT TEST event.recorder', function() {
3232
const actual = createBuffer(root);
3333
expect(actual).toBe(clientNode as HTMLElement);
3434
});
35+
36+
it('should add the "ng-non-bindable" attribute to serverNode', function () {
37+
const root = <ServerClientRoot> {
38+
serverSelector: 'div',
39+
serverNode: getMockElement()
40+
};
41+
42+
createBuffer(root);
43+
expect(root.serverNode!.hasAttribute('ng-non-bindable')).toBe(true);
44+
});
3545
});
3646

3747
describe('getSelection()', function () {

src/lib/api/event.recorder.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -330,6 +330,9 @@ export function createBuffer(root: ServerClientRoot): HTMLElement {
330330
// insert the client node before the server and return it
331331
serverNode.parentNode.insertBefore(rootClientNode, serverNode);
332332

333+
// mark server node as not to be touched by AngularJS - needed for ngUpgrade
334+
serverNode.setAttribute('ng-non-bindable', '');
335+
333336
// return the rootClientNode
334337
return rootClientNode;
335338
}

src/lib/common/preboot.mocks.ts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,22 @@ export function getMockOptions(): PrebootOptions {
1616

1717
export function getMockElement(): Element {
1818
return {
19+
___attributes: new Map<string, string>(),
1920
cloneNode: () => { return { style: {} }; },
2021
parentNode: {
2122
insertBefore: function () {}
23+
},
24+
hasAttribute(key: string) {
25+
return this.___attributes.has(key);
26+
},
27+
getAttribute(key: string) {
28+
return this.___attributes.get(key);
29+
},
30+
setAttribute(key: string, value: string) {
31+
this.___attributes.set(key, value);
32+
},
33+
removeAttribute(key: string) {
34+
this.___attributes.delete(key);
2235
}
2336
} as any as Element;
2437
}

0 commit comments

Comments
 (0)