Issue 1:
I'm submitting a ... (check one with "x")

[ ] bug report => search github for a similar issue or PR before submitting
[x] feature request
[ ] support request => Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question

Current behavior

Currently, the renderModuleFactory method generated HTML depends the client main.bundle.js. If use this HTML as pre-rendered version, while browser load the HTML, Angular would re-render the DOM.

Expected behavior

The platform-server package can provide the method to generate the pre-rendered HTML version

Minimal reproduction of the problem with instructions

the entry file: main.server.ts:

import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { AppServerModule } from './app/app.server.module';
import { ROUTES } from './routes';

app.engine('html', ngExpressEngine({
bootstrap: AppServerModule
}));

app.set('view engine', 'html');
app.set('views', 'dist/client');

ROUTES.forEach(route => {
app.get(route, (req, res) => {
console.time(`GET: ${req.originalUrl}`);
res.render('index', {
req: req,
res: res,
preboot: true,
});
console.timeEnd(`GET: ${req.originalUrl}`);
});
});

After the express server start, using curl to get the generated HTML as pre-rendered version.

upload the HTML file to CDN network, open the HTML URL in browser, Angular would re-render the DOM.

What is the motivation / use case for changing the behavior?

In some use case, we only have static web server & we also care about the SEO, so we choose Angular Universal.

And we want to use the pre-rendered HTML to boot app.

Please tell us about your environment:

Angular version: 4.1.2

Browser: [all ]

Language: [TypeScript 2.3.2]

Node (for AoT issues): node --version = v6.9.1

Reference: #13822

Issue 2:
I'm recapping a discussion I just had with @alxhub and @tbosch. This is mostly Tobias' design.

I'm submitting a ... (check one with "x")

[x] feature request

Current behavior

Typically when a page is pre-rendered, such as with Universal, the Angular application bootstraps in the browser, then blows away the pre-rendered content, and replaces it with newly created nodes. The DOM tree that is pre-rendered is often very similar, or identical to the DOM tree created on the front end, but there are issues that make it difficult for Angular to take over the existing DOM tree (otherwise referred to as DOM hydration).

The actual handoff of destroying the old DOM and showing the new DOM is pretty fast and seamless, so it's not necessarily a major UX issue in and of itself. Where it becomes problematic is in cases like ads that load in iframes (which is pretty much all display ads). If these ad iframes are pre-rendered -- which is a business requirement for many publishers -- and the iframe gets moved in the DOM, the iframe will refresh. This causes some ad networks to suspect abuse, as if publishers are trying to sneak more ad views.

Why Not Use the Already-Rendered DOM?

One issue is that with asynchronicity+conditional DOM (i.e. *ngIf="data | async"), the tree in the client may be rendered before the condition is truthy, whereas the pre-rendered version may have the full tree with async data resolved.

Another challenge is that text nodes are not able to be selected by CSS selectors, which would mean the renderer would have to rely on child ordering in order to associate pre-rendered nodes with client-rendered nodes (which is not always correct). Similar challenge goes for elements in an *ngFor, the order must be assumed to be identical.

The renderer would also be responsible for cleaning up pre-rendered orphan nodes. i.e. if 30 items in an *ngFor were pre-rendered, but only 20 were rendered in the client, the additional 10 nodes would need to be removed to prevent unexpected behavior.

Proposal: Optional, explicit, partial DOM Hydration

Allow setting a user-specified attribute on elements to associate the pre-rendered version with client-rendered version. If the renderer comes to a node that it can't associate with an existing node, it will blow away the node and re-create it. The developer would be responsible for setting these ids on the elements they care about. Example:

import { HydrateDomConfig, NgModule } from '@angular/core';

@NgModule({
providers: [
{
provide: HydrateDomConfig,
useValue: {
hydrate: true, // default false for backwards compat
attribute: 'pid', // default 'id'
}
}
]
})

Component:

@Component({
template: `
<div pid="header">
<header-ad pid="header-ad"></header-ad>
<div>
<!-- this will get blown away and re-created since it lacks attribute -->
</div>
</div>
`
})

This design allows the DomRenderer to traverse the DOM tree and match elements for each branch starting at the root until it can't go any deeper, at which point it would blow away the descendants and re-create them.

Text nodes would all be destroyed and re-created with this design, as well as any node that doesn't have the set attribute, pid.

I don't expect that the rendering would be user-perceivable, other than if there are discrepancies between pre-rendered and client-rendered DOM, but that's a concern even without this feature.

CC @gdi2290 @pxwise

@tbosch & @alxhub please feel free to add anything I missed (or misrepresented).