66 * found in the LICENSE file at https://angular.dev/license
77 */
88
9+ import { APP_BASE_HREF , PlatformLocation } from '@angular/common' ;
910import {
1011 ApplicationRef ,
1112 type PlatformRef ,
@@ -19,8 +20,9 @@ import {
1920 platformServer ,
2021 ɵrenderInternal as renderInternal ,
2122} from '@angular/platform-server' ;
23+ import { Router } from '@angular/router' ;
2224import { Console } from '../console' ;
23- import { stripIndexHtmlFromURL } from './url' ;
25+ import { joinUrlParts , stripIndexHtmlFromURL } from './url' ;
2426
2527/**
2628 * Represents the bootstrap mechanism for an Angular application.
@@ -35,28 +37,25 @@ export type AngularBootstrap = Type<unknown> | (() => Promise<ApplicationRef>);
3537 * Renders an Angular application or module to an HTML string.
3638 *
3739 * This function determines whether the provided `bootstrap` value is an Angular module
38- * or a bootstrap function and calls the appropriate rendering method (`renderModule` or
39- * `renderApplication`) based on that determination.
40+ * or a bootstrap function and invokes the appropriate rendering method (`renderModule` or `renderApplication`).
4041 *
41- * @param html - The HTML string to be used as the initial document content.
42- * @param bootstrap - Either an Angular module type or a function that returns a promise
43- * resolving to an `ApplicationRef`.
44- * @param url - The URL of the application. This is used for server-side rendering to
45- * correctly handle route-based rendering.
46- * @param platformProviders - An array of platform providers to be used during the
47- * rendering process.
48- * @param serverContext - A string representing the server context, used to provide additional
49- * context or metadata during server-side rendering.
50- * @returns A promise resolving to an object containing a `content` method, which returns a
51- * promise that resolves to the rendered HTML string.
42+ * @param html - The initial HTML document content.
43+ * @param bootstrap - An Angular module type or a function returning a promise that resolves to an `ApplicationRef`.
44+ * @param url - The application URL, used for route-based rendering in SSR.
45+ * @param platformProviders - An array of platform providers for the rendering process.
46+ * @param serverContext - A string representing the server context, providing additional metadata for SSR.
47+ * @returns A promise resolving to an object containing:
48+ * - `hasNavigationError`: Indicates if a navigation error occurred.
49+ * - `redirectTo`: (Optional) The redirect URL if a navigation redirect occurred.
50+ * - `content`: A function returning a promise that resolves to the rendered HTML string.
5251 */
5352export async function renderAngular (
5453 html : string ,
5554 bootstrap : AngularBootstrap ,
5655 url : URL ,
5756 platformProviders : StaticProvider [ ] ,
5857 serverContext : string ,
59- ) : Promise < { content : ( ) => Promise < string > } > {
58+ ) : Promise < { hasNavigationError : boolean ; redirectTo ?: string ; content : ( ) => Promise < string > } > {
6059 // A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
6160 const urlToRender = stripIndexHtmlFromURL ( url ) . toString ( ) ;
6261 const platformRef = platformServer ( [
@@ -82,6 +81,9 @@ export async function renderAngular(
8281 ...platformProviders ,
8382 ] ) ;
8483
84+ let redirectTo : string | undefined ;
85+ let hasNavigationError = true ;
86+
8587 try {
8688 let applicationRef : ApplicationRef ;
8789 if ( isNgModule ( bootstrap ) ) {
@@ -94,7 +96,29 @@ export async function renderAngular(
9496 // Block until application is stable.
9597 await applicationRef . whenStable ( ) ;
9698
99+ // TODO(alanagius): Find a way to avoid rendering here especially for redirects as any output will be discarded.
100+ const envInjector = applicationRef . injector ;
101+ const router = envInjector . get ( Router ) ;
102+ const lastSuccessfulNavigation = router . lastSuccessfulNavigation ;
103+
104+ if ( lastSuccessfulNavigation ?. finalUrl ) {
105+ hasNavigationError = false ;
106+
107+ const { finalUrl, initialUrl } = lastSuccessfulNavigation ;
108+ const finalUrlStringified = finalUrl . toString ( ) ;
109+
110+ if ( initialUrl . toString ( ) !== finalUrlStringified ) {
111+ const baseHref =
112+ envInjector . get ( APP_BASE_HREF , null , { optional : true } ) ??
113+ envInjector . get ( PlatformLocation ) . getBaseHrefFromDOM ( ) ;
114+
115+ redirectTo = joinUrlParts ( baseHref , finalUrlStringified ) ;
116+ }
117+ }
118+
97119 return {
120+ hasNavigationError,
121+ redirectTo,
98122 content : ( ) =>
99123 new Promise < string > ( ( resolve , reject ) => {
100124 // Defer rendering to the next event loop iteration to avoid blocking, as most operations in `renderInternal` are synchronous.
@@ -110,6 +134,10 @@ export async function renderAngular(
110134 await asyncDestroyPlatform ( platformRef ) ;
111135
112136 throw error ;
137+ } finally {
138+ if ( hasNavigationError || redirectTo ) {
139+ void asyncDestroyPlatform ( platformRef ) ;
140+ }
113141 }
114142}
115143
@@ -134,7 +162,10 @@ export function isNgModule(value: AngularBootstrap): value is Type<unknown> {
134162function asyncDestroyPlatform ( platformRef : PlatformRef ) : Promise < void > {
135163 return new Promise ( ( resolve ) => {
136164 setTimeout ( ( ) => {
137- platformRef . destroy ( ) ;
165+ if ( ! platformRef . destroyed ) {
166+ platformRef . destroy ( ) ;
167+ }
168+
138169 resolve ( ) ;
139170 } , 0 ) ;
140171 } ) ;
0 commit comments