66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9- import { ɵConsole } from '@angular/core' ;
10- import type { ApplicationRef , StaticProvider , Type } from '@angular/core' ;
119import {
10+ ApplicationRef ,
11+ type PlatformRef ,
12+ type StaticProvider ,
13+ type Type ,
14+ ɵConsole ,
15+ } from '@angular/core' ;
16+ import {
17+ INITIAL_CONFIG ,
1218 ɵSERVER_CONTEXT as SERVER_CONTEXT ,
13- renderApplication ,
14- renderModule ,
19+ platformServer ,
20+ ɵrenderInternal as renderInternal ,
1521} from '@angular/platform-server' ;
1622import { Console } from '../console' ;
1723import { stripIndexHtmlFromURL } from './url' ;
@@ -41,16 +47,26 @@ export type AngularBootstrap = Type<unknown> | (() => Promise<ApplicationRef>);
4147 * rendering process.
4248 * @param serverContext - A string representing the server context, used to provide additional
4349 * context or metadata during server-side rendering.
44- * @returns A promise that resolves to a string containing the rendered HTML.
50+ * @returns A promise resolving to an object containing a `content` method, which returns a
51+ * promise that resolves to the rendered HTML string.
4552 */
46- export function renderAngular (
53+ export async function renderAngular (
4754 html : string ,
4855 bootstrap : AngularBootstrap ,
4956 url : URL ,
5057 platformProviders : StaticProvider [ ] ,
5158 serverContext : string ,
52- ) : Promise < string > {
53- const providers = [
59+ ) : Promise < { content : ( ) => Promise < string > } > {
60+ // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
61+ const urlToRender = stripIndexHtmlFromURL ( url ) . toString ( ) ;
62+ const platformRef = platformServer ( [
63+ {
64+ provide : INITIAL_CONFIG ,
65+ useValue : {
66+ url : urlToRender ,
67+ document : html ,
68+ } ,
69+ } ,
5470 {
5571 provide : SERVER_CONTEXT ,
5672 useValue : serverContext ,
@@ -64,22 +80,34 @@ export function renderAngular(
6480 useFactory : ( ) => new Console ( ) ,
6581 } ,
6682 ...platformProviders ,
67- ] ;
83+ ] ) ;
6884
69- // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
70- const urlToRender = stripIndexHtmlFromURL ( url ) . toString ( ) ;
85+ try {
86+ let applicationRef : ApplicationRef ;
87+ if ( isNgModule ( bootstrap ) ) {
88+ const moduleRef = await platformRef . bootstrapModule ( bootstrap ) ;
89+ applicationRef = moduleRef . injector . get ( ApplicationRef ) ;
90+ } else {
91+ applicationRef = await bootstrap ( ) ;
92+ }
7193
72- return isNgModule ( bootstrap )
73- ? renderModule ( bootstrap , {
74- url : urlToRender ,
75- document : html ,
76- extraProviders : providers ,
77- } )
78- : renderApplication ( bootstrap , {
79- url : urlToRender ,
80- document : html ,
81- platformProviders : providers ,
82- } ) ;
94+ // Block until application is stable.
95+ await applicationRef . whenStable ( ) ;
96+
97+ return {
98+ content : async ( ) => {
99+ try {
100+ return renderInternal ( platformRef , applicationRef ) ;
101+ } finally {
102+ await asyncDestroyPlatform ( platformRef ) ;
103+ }
104+ } ,
105+ } ;
106+ } catch ( error ) {
107+ await asyncDestroyPlatform ( platformRef ) ;
108+
109+ throw error ;
110+ }
83111}
84112
85113/**
@@ -93,3 +121,18 @@ export function renderAngular(
93121export function isNgModule ( value : AngularBootstrap ) : value is Type < unknown > {
94122 return 'ɵmod' in value ;
95123}
124+
125+ /**
126+ * Gracefully destroys the application in a macrotask, allowing pending promises to resolve
127+ * and surfacing any potential errors to the user.
128+ *
129+ * @param platformRef - The platform reference to be destroyed.
130+ */
131+ function asyncDestroyPlatform ( platformRef : PlatformRef ) : Promise < void > {
132+ return new Promise ( ( resolve ) => {
133+ setTimeout ( ( ) => {
134+ platformRef . destroy ( ) ;
135+ resolve ( ) ;
136+ } , 0 ) ;
137+ } ) ;
138+ }
0 commit comments