6
6
* found in the LICENSE file at https://angular.dev/license
7
7
*/
8
8
9
+ import { APP_BASE_HREF , PlatformLocation } from '@angular/common' ;
9
10
import {
10
11
ApplicationRef ,
11
12
type PlatformRef ,
@@ -19,8 +20,9 @@ import {
19
20
platformServer ,
20
21
ɵrenderInternal as renderInternal ,
21
22
} from '@angular/platform-server' ;
23
+ import { Router } from '@angular/router' ;
22
24
import { Console } from '../console' ;
23
- import { stripIndexHtmlFromURL } from './url' ;
25
+ import { joinUrlParts , stripIndexHtmlFromURL } from './url' ;
24
26
25
27
/**
26
28
* Represents the bootstrap mechanism for an Angular application.
@@ -35,28 +37,25 @@ export type AngularBootstrap = Type<unknown> | (() => Promise<ApplicationRef>);
35
37
* Renders an Angular application or module to an HTML string.
36
38
*
37
39
* 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`).
40
41
*
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.
52
51
*/
53
52
export async function renderAngular (
54
53
html : string ,
55
54
bootstrap : AngularBootstrap ,
56
55
url : URL ,
57
56
platformProviders : StaticProvider [ ] ,
58
57
serverContext : string ,
59
- ) : Promise < { content : ( ) => Promise < string > } > {
58
+ ) : Promise < { hasNavigationError : boolean ; redirectTo ?: string ; content : ( ) => Promise < string > } > {
60
59
// A request to `http://www.example.com/page/index.html` will render the Angular route corresponding to `http://www.example.com/page`.
61
60
const urlToRender = stripIndexHtmlFromURL ( url ) . toString ( ) ;
62
61
const platformRef = platformServer ( [
@@ -82,6 +81,9 @@ export async function renderAngular(
82
81
...platformProviders ,
83
82
] ) ;
84
83
84
+ let redirectTo : string | undefined ;
85
+ let hasNavigationError = true ;
86
+
85
87
try {
86
88
let applicationRef : ApplicationRef ;
87
89
if ( isNgModule ( bootstrap ) ) {
@@ -94,7 +96,29 @@ export async function renderAngular(
94
96
// Block until application is stable.
95
97
await applicationRef . whenStable ( ) ;
96
98
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
+
97
119
return {
120
+ hasNavigationError,
121
+ redirectTo,
98
122
content : ( ) =>
99
123
new Promise < string > ( ( resolve , reject ) => {
100
124
// 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(
110
134
await asyncDestroyPlatform ( platformRef ) ;
111
135
112
136
throw error ;
137
+ } finally {
138
+ if ( hasNavigationError || redirectTo ) {
139
+ void asyncDestroyPlatform ( platformRef ) ;
140
+ }
113
141
}
114
142
}
115
143
@@ -134,7 +162,10 @@ export function isNgModule(value: AngularBootstrap): value is Type<unknown> {
134
162
function asyncDestroyPlatform ( platformRef : PlatformRef ) : Promise < void > {
135
163
return new Promise ( ( resolve ) => {
136
164
setTimeout ( ( ) => {
137
- platformRef . destroy ( ) ;
165
+ if ( ! platformRef . destroyed ) {
166
+ platformRef . destroy ( ) ;
167
+ }
168
+
138
169
resolve ( ) ;
139
170
} , 0 ) ;
140
171
} ) ;
0 commit comments