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 ( [
@@ -91,10 +90,38 @@ export async function renderAngular(
91
90
applicationRef = await bootstrap ( ) ;
92
91
}
93
92
94
- // Block until application is stable.
95
- await applicationRef . whenStable ( ) ;
93
+ const envInjector = applicationRef . injector ;
94
+ const router = envInjector . get ( Router ) ;
95
+ const lastSuccessfulNavigation = router . lastSuccessfulNavigation ;
96
+
97
+ let redirectTo : string | undefined ;
98
+ let hasNavigationError = true ;
99
+
100
+ if ( lastSuccessfulNavigation ?. finalUrl ) {
101
+ hasNavigationError = false ;
102
+
103
+ const { finalUrl, initialUrl } = lastSuccessfulNavigation ;
104
+ const finalUrlStringified = finalUrl . toString ( ) ;
105
+
106
+ if ( initialUrl . toString ( ) !== finalUrlStringified ) {
107
+ const baseHref =
108
+ envInjector . get ( APP_BASE_HREF , null , { optional : true } ) ??
109
+ envInjector . get ( PlatformLocation ) . getBaseHrefFromDOM ( ) ;
110
+
111
+ redirectTo = joinUrlParts ( baseHref , finalUrlStringified ) ;
112
+ }
113
+ }
114
+
115
+ if ( hasNavigationError || redirectTo ) {
116
+ void asyncDestroyPlatform ( platformRef ) ;
117
+ } else {
118
+ // Block until application is stable.
119
+ await applicationRef . whenStable ( ) ;
120
+ }
96
121
97
122
return {
123
+ hasNavigationError,
124
+ redirectTo,
98
125
content : ( ) =>
99
126
new Promise < string > ( ( resolve , reject ) => {
100
127
// Defer rendering to the next event loop iteration to avoid blocking, as most operations in `renderInternal` are synchronous.
0 commit comments