/** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.io/license */ import {animate, AnimationBuilder, state, style, transition, trigger} from '@angular/animations'; import {DOCUMENT, isPlatformServer, PlatformLocation, ɵgetDOM as getDOM} from '@angular/common'; import {HTTP_INTERCEPTORS, HttpClient, HttpClientModule, HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http'; import {HttpClientTestingModule, HttpTestingController} from '@angular/common/http/testing'; import {ApplicationRef, CompilerFactory, Component, destroyPlatform, getPlatform, HostBinding, HostListener, importProvidersFrom, Inject, Injectable, Input, NgModule, NgZone, OnInit, PLATFORM_ID, PlatformRef, Type, ViewEncapsulation} from '@angular/core'; import {inject, TestBed, waitForAsync} from '@angular/core/testing'; import {BrowserModule, makeStateKey, Title, TransferState} from '@angular/platform-browser'; import {BEFORE_APP_SERIALIZED, INITIAL_CONFIG, platformDynamicServer, PlatformState, renderModule, renderModuleFactory, ServerModule, ServerTransferStateModule} from '@angular/platform-server'; import {Observable} from 'rxjs'; import {first} from 'rxjs/operators'; import {renderApplication, SERVER_CONTEXT} from '../src/utils'; function createMyServerApp(standalone: boolean) { @Component({ standalone, selector: 'app', template: `Works!`, }) class MyServerApp { } return MyServerApp; } const MyServerApp = createMyServerApp(false); const MyServerAppStandalone = createMyServerApp(true); @NgModule({ declarations: [MyServerApp], exports: [MyServerApp], }) export class MyServerAppModule { } @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, ServerModule], }) class ExampleModule { } function getTitleRenderHook(doc: any) { return () => { // Set the title as part of the render hook. doc.title = 'RenderHook'; }; } function exceptionRenderHook() { throw new Error('error'); } function getMetaRenderHook(doc: any) { return () => { // Add a meta tag before rendering the document. const metaElement = doc.createElement('meta'); metaElement.setAttribute('name', 'description'); doc.head.appendChild(metaElement); }; } function getAsyncTitleRenderHook(doc: any) { return () => { // Async set the title as part of the render hook. return new Promise(resolve => { setTimeout(() => { doc.title = 'AsyncRenderHook'; resolve(); }); }); }; } function asyncRejectRenderHook() { return () => { return new Promise((_resolve, reject) => { setTimeout(() => { reject('reject'); }); }); }; } const RenderHookProviders = [ {provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]} ]; @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule], providers: [...RenderHookProviders], }) class RenderHookModule { } const MultiRenderHookProviders = [ {provide: BEFORE_APP_SERIALIZED, useFactory: getTitleRenderHook, multi: true, deps: [DOCUMENT]}, {provide: BEFORE_APP_SERIALIZED, useValue: exceptionRenderHook, multi: true}, {provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]}, ]; @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule], providers: [...MultiRenderHookProviders], }) class MultiRenderHookModule { } const AsyncRenderHookProviders = [ { provide: BEFORE_APP_SERIALIZED, useFactory: getAsyncTitleRenderHook, multi: true, deps: [DOCUMENT] }, ]; @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule], providers: [...AsyncRenderHookProviders], }) class AsyncRenderHookModule { } const AsyncMultiRenderHookProviders = [ {provide: BEFORE_APP_SERIALIZED, useFactory: getMetaRenderHook, multi: true, deps: [DOCUMENT]}, { provide: BEFORE_APP_SERIALIZED, useFactory: getAsyncTitleRenderHook, multi: true, deps: [DOCUMENT] }, {provide: BEFORE_APP_SERIALIZED, useFactory: asyncRejectRenderHook, multi: true}, ]; @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, BrowserModule.withServerTransition({appId: 'render-hook'}), ServerModule], providers: [...AsyncMultiRenderHookProviders], }) class AsyncMultiRenderHookModule { } @Component({selector: 'app', template: `Works too!`}) class MyServerApp2 { } @NgModule({declarations: [MyServerApp2], imports: [ServerModule], bootstrap: [MyServerApp2]}) class ExampleModule2 { } @Component({selector: 'app', template: ``}) class TitleApp { constructor(private title: Title) {} ngOnInit() { this.title.setTitle('Test App Title'); } } @NgModule({declarations: [TitleApp], imports: [ServerModule], bootstrap: [TitleApp]}) class TitleAppModule { } function createMyAsyncServerApp(standalone: boolean) { @Component({ selector: 'app', template: '{{text}}

', standalone, }) class MyAsyncServerApp { text = ''; h1 = ''; @HostListener('window:scroll') track() { console.error('scroll'); } ngOnInit() { Promise.resolve(null).then(() => setTimeout(() => { this.text = 'Works!'; this.h1 = 'fine'; }, 10)); } } return MyAsyncServerApp; } const MyAsyncServerApp = createMyAsyncServerApp(false); const MyAsyncServerAppStandalone = createMyAsyncServerApp(true); @NgModule({ declarations: [MyAsyncServerApp], imports: [BrowserModule.withServerTransition({appId: 'async-server'}), ServerModule], bootstrap: [MyAsyncServerApp] }) class AsyncServerModule { } function createSVGComponent(standalone: boolean) { @Component({ selector: 'app', template: '', standalone, }) class SVGComponent { } return SVGComponent; } const SVGComponent = createSVGComponent(false); const SVGComponentStandalone = createSVGComponent(true); @NgModule({ declarations: [SVGComponent], imports: [BrowserModule.withServerTransition({appId: 'svg-server'}), ServerModule], bootstrap: [SVGComponent] }) class SVGServerModule { } function createMyAnimationApp(standalone: boolean) { @Component({ standalone, selector: 'app', template: `
{{text}}
`, animations: [trigger( 'myAnimation', [ state('void', style({'opacity': '0'})), state('active', style({ 'opacity': '1', // simple supported property 'font-weight': 'bold', // property with dashed name 'transform': 'translate3d(0, 0, 0)', // not natively supported by Domino })), transition('void => *', [animate('0ms')]), ], )] }) class MyAnimationApp { state = 'active'; constructor(private builder: AnimationBuilder) {} text = 'Works!'; } return MyAnimationApp; } const MyAnimationApp = createMyAnimationApp(false); const MyAnimationAppStandalone = createMyAnimationApp(true); @NgModule({ declarations: [MyAnimationApp], imports: [BrowserModule.withServerTransition({appId: 'anim-server'}), ServerModule], bootstrap: [MyAnimationApp] }) class AnimationServerModule { } function createMyStylesApp(standalone: boolean) { @Component({ standalone, selector: 'app', template: `
Works!
`, styles: ['div {color: blue; } :host { color: red; }'] }) class MyStylesApp { } return MyStylesApp; } const MyStylesApp = createMyStylesApp(false); const MyStylesAppStandalone = createMyStylesApp(true); @NgModule({ declarations: [MyStylesApp], imports: [BrowserModule.withServerTransition({appId: 'example-styles'}), ServerModule], bootstrap: [MyStylesApp] }) class ExampleStylesModule { } @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, ServerModule, HttpClientModule, HttpClientTestingModule], }) export class HttpClientExampleModule { } @Injectable() export class MyHttpInterceptor implements HttpInterceptor { constructor(private http: HttpClient) {} intercept(req: HttpRequest, next: HttpHandler): Observable> { return next.handle(req); } } @NgModule({ bootstrap: [MyServerApp], imports: [MyServerAppModule, ServerModule, HttpClientModule, HttpClientTestingModule], providers: [ {provide: HTTP_INTERCEPTORS, multi: true, useClass: MyHttpInterceptor}, ], }) export class HttpInterceptorExampleModule { } @Component({selector: 'app', template: ``}) class ImageApp { } @NgModule({declarations: [ImageApp], imports: [ServerModule], bootstrap: [ImageApp]}) class ImageExampleModule { } function createShadowDomEncapsulationApp(standalone: boolean) { @Component({ standalone, selector: 'app', template: 'Shadow DOM works', encapsulation: ViewEncapsulation.ShadowDom, styles: [':host { color: red; }'] }) class ShadowDomEncapsulationApp { } return ShadowDomEncapsulationApp; } const ShadowDomEncapsulationApp = createShadowDomEncapsulationApp(false); const ShadowDomEncapsulationAppStandalone = createShadowDomEncapsulationApp(true); @NgModule({ declarations: [ShadowDomEncapsulationApp], imports: [BrowserModule.withServerTransition({appId: 'test'}), ServerModule], bootstrap: [ShadowDomEncapsulationApp] }) class ShadowDomExampleModule { } function createFalseAttributesComponents(standalone: boolean) { @Component({ standalone, selector: 'my-child', template: 'Works!', }) class MyChildComponent { // TODO(issue/24571): remove '!'. @Input() public attr!: boolean; } @Component({ standalone, selector: 'app', template: '', imports: standalone ? [MyChildComponent] : [], }) class MyHostComponent { } return [MyHostComponent, MyChildComponent]; } const [MyHostComponent, MyChildComponent] = createFalseAttributesComponents(false); const [MyHostComponentStandalone, _] = createFalseAttributesComponents(true); @NgModule({ declarations: [MyHostComponent, MyChildComponent], bootstrap: [MyHostComponent], imports: [ServerModule, BrowserModule.withServerTransition({appId: 'false-attributes'})] }) class FalseAttributesModule { } @Component({selector: 'app', template: '
'}) class InnerTextComponent { foo = 'Some text'; } @NgModule({ declarations: [InnerTextComponent], bootstrap: [InnerTextComponent], imports: [ServerModule, BrowserModule.withServerTransition({appId: 'inner-text'})] }) class InnerTextModule { } function createMyInputComponent(standalone: boolean) { @Component({ standalone, selector: 'app', template: '', }) class MyInputComponent { @Input() name = ''; } return MyInputComponent; } const MyInputComponent = createMyInputComponent(false); const MyInputComponentStandalone = createMyInputComponent(true); @NgModule({ declarations: [MyInputComponent], bootstrap: [MyInputComponent], imports: [ServerModule, BrowserModule.withServerTransition({appId: 'name-attributes'})] }) class NameModule { } function createHTMLTypesApp(standalone: boolean) { @Component({ standalone, selector: 'app', template: '
', }) class HTMLTypesApp { html = 'foo bar'; constructor(@Inject(DOCUMENT) doc: Document) {} } return HTMLTypesApp; } const HTMLTypesApp = createHTMLTypesApp(false); const HTMLTypesAppStandalone = createHTMLTypesApp(true); @NgModule({ declarations: [HTMLTypesApp], imports: [BrowserModule.withServerTransition({appId: 'inner-html'}), ServerModule], bootstrap: [HTMLTypesApp] }) class HTMLTypesModule { } const TEST_KEY = makeStateKey('test'); const STRING_KEY = makeStateKey('testString'); @Component({selector: 'app', template: 'Works!'}) class TransferComponent { constructor(private transferStore: TransferState) {} ngOnInit() { this.transferStore.set(TEST_KEY, 10); } } @Component({selector: 'esc-app', template: 'Works!'}) class EscapedComponent { constructor(private transferStore: TransferState) {} ngOnInit() { this.transferStore.set(STRING_KEY, '