<p dir="auto"><strong>I'm submitting a ...</strong>  (check one with "x")</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="[ ] bug report =&gt; search github for a similar issue or PR before submitting
[x] feature request
[ ] support request =&gt; Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question"><pre class="notranslate"><code class="notranslate">[ ] bug report =&gt; search github for a similar issue or PR before submitting
[x] feature request
[ ] support request =&gt; Please do not submit support request here, instead see https://github.com/angular/angular/blob/master/CONTRIBUTING.md#question
</code></pre></div>
<p dir="auto"><strong>Current behavior</strong></p>

<p dir="auto">Currently, the <code class="notranslate">renderModuleFactory</code> method generated HTML depends the client <code class="notranslate">main.bundle.js</code>. If use this HTML as pre-rendered version, while browser load the HTML, Angular would re-render the DOM.</p>
<p dir="auto"><strong>Expected behavior</strong></p>

<p dir="auto">The platform-server package can provide the method to generate the pre-rendered HTML version</p>
<p dir="auto"><strong>Minimal reproduction of the problem with instructions</strong></p>

<p dir="auto">the entry file: main.server.ts:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="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 =&gt; {
  app.get(route, (req, res) =&gt; {
    console.time(`GET: ${req.originalUrl}`);
    res.render('index', {
      req: req,
      res: res,
      preboot: true,
    });
    console.timeEnd(`GET: ${req.originalUrl}`);
  });
});"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-s1">ngExpressEngine</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@nguniversal/express-engine'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-c1">*</span> <span class="pl-k">as</span> <span class="pl-s1">express</span> <span class="pl-k">from</span> <span class="pl-s">'express'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-smi">AppServerModule</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'./app/app.server.module'</span><span class="pl-kos">;</span>
<span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-smi">ROUTES</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'./routes'</span><span class="pl-kos">;</span>

<span class="pl-s1">app</span><span class="pl-kos">.</span><span class="pl-en">engine</span><span class="pl-kos">(</span><span class="pl-s">'html'</span><span class="pl-kos">,</span> <span class="pl-en">ngExpressEngine</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
  <span class="pl-c1">bootstrap</span>: <span class="pl-smi">AppServerModule</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-s1">app</span><span class="pl-kos">.</span><span class="pl-en">set</span><span class="pl-kos">(</span><span class="pl-s">'view engine'</span><span class="pl-kos">,</span> <span class="pl-s">'html'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-s1">app</span><span class="pl-kos">.</span><span class="pl-en">set</span><span class="pl-kos">(</span><span class="pl-s">'views'</span><span class="pl-kos">,</span> <span class="pl-s">'dist/client'</span><span class="pl-kos">)</span><span class="pl-kos">;</span>

<span class="pl-smi">ROUTES</span><span class="pl-kos">.</span><span class="pl-en">forEach</span><span class="pl-kos">(</span><span class="pl-s1">route</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
  <span class="pl-s1">app</span><span class="pl-kos">.</span><span class="pl-en">get</span><span class="pl-kos">(</span><span class="pl-s1">route</span><span class="pl-kos">,</span> <span class="pl-kos">(</span><span class="pl-s1">req</span><span class="pl-kos">,</span> <span class="pl-s1">res</span><span class="pl-kos">)</span> <span class="pl-c1">=&gt;</span> <span class="pl-kos">{</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">time</span><span class="pl-kos">(</span><span class="pl-s">`GET: <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-c1">originalUrl</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-s1">res</span><span class="pl-kos">.</span><span class="pl-en">render</span><span class="pl-kos">(</span><span class="pl-s">'index'</span><span class="pl-kos">,</span> <span class="pl-kos">{</span>
      <span class="pl-c1">req</span>: <span class="pl-s1">req</span><span class="pl-kos">,</span>
      <span class="pl-c1">res</span>: <span class="pl-s1">res</span><span class="pl-kos">,</span>
      <span class="pl-c1">preboot</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span>
    <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
    <span class="pl-smi">console</span><span class="pl-kos">.</span><span class="pl-en">timeEnd</span><span class="pl-kos">(</span><span class="pl-s">`GET: <span class="pl-s1"><span class="pl-kos">${</span><span class="pl-s1">req</span><span class="pl-kos">.</span><span class="pl-c1">originalUrl</span><span class="pl-kos">}</span></span>`</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
  <span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span><span class="pl-kos">;</span></pre></div>
<p dir="auto">After the express server start, using <code class="notranslate">curl</code> to get the generated HTML as pre-rendered version.<br>
upload the HTML file to CDN network, open the HTML URL in browser, Angular would re-render the DOM.</p>
<p dir="auto"><strong>What is the motivation / use case for changing the behavior?</strong></p>

<p dir="auto">In some use case, we only have static web server &amp; we also care about the SEO, so we choose Angular Universal.<br>
And we want to use the pre-rendered HTML to boot app.</p>
<p dir="auto"><strong>Please tell us about your environment:</strong></p>

<ul dir="auto">
<li><strong>Angular version:</strong> 4.1.2</li>
</ul>

<ul dir="auto">
<li><strong>Browser:</strong> [all ]</li>
</ul>

<ul dir="auto">
<li>
<p dir="auto"><strong>Language:</strong> [TypeScript 2.3.2]</p>
</li>
<li>
<p dir="auto"><strong>Node (for AoT issues):</strong> <code class="notranslate">node --version</code> = v6.9.1</p>
</li>
</ul>
<p dir="auto">Reference: <a class="issue-link js-issue-link" data-error-text="Failed to load title" data-id="199336473" data-permission-text="Title is private" data-url="https://github.com/angular/angular/issues/13822" data-hovercard-type="issue" data-hovercard-url="/angular/angular/issues/13822/hovercard" href="https://github.com/angular/angular/issues/13822">#13822</a></p>
<p dir="auto">I'm recapping a discussion I just had with <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/alxhub/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/alxhub">@alxhub</a> and <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/tbosch/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/tbosch">@tbosch</a>. This is mostly Tobias' design.</p>

<p dir="auto"><strong>I'm submitting a ...</strong>  (check one with "x")</p>
<div class="snippet-clipboard-content notranslate position-relative overflow-auto" data-snippet-clipboard-copy-content="[x] feature request"><pre class="notranslate"><code class="notranslate">[x] feature request
</code></pre></div>
<p dir="auto"><strong>Current behavior</strong></p>

<p dir="auto">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).</p>
<p dir="auto">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.</p>
<p dir="auto"><strong>Why Not Use the Already-Rendered DOM?</strong></p>
<p dir="auto">One issue is that with asynchronicity+conditional DOM (i.e. <code class="notranslate">*ngIf="data | async"</code>), 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.</p>
<p dir="auto">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 <code class="notranslate">*ngFor</code>, the order must be assumed to be identical.</p>
<p dir="auto">The renderer would also be responsible for cleaning up pre-rendered orphan nodes. i.e. if 30 items in an <code class="notranslate">*ngFor</code> were pre-rendered, but only 20 were rendered in the client, the additional 10 nodes would need to be removed to prevent unexpected behavior.</p>
<p dir="auto"><strong>Proposal: Optional, explicit, partial DOM Hydration</strong></p>
<p dir="auto">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:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="import { HydrateDomConfig, NgModule } from '@angular/core';

@NgModule({
  providers: [
    { 
      provide: HydrateDomConfig, 
      useValue: {
        hydrate: true, // default false for backwards compat
        attribute: 'pid', // default 'id'
      } 
    }
  ]
})"><pre class="notranslate"><span class="pl-k">import</span> <span class="pl-kos">{</span> <span class="pl-smi">HydrateDomConfig</span><span class="pl-kos">,</span> <span class="pl-smi">NgModule</span> <span class="pl-kos">}</span> <span class="pl-k">from</span> <span class="pl-s">'@angular/core'</span><span class="pl-kos">;</span>

@<span class="pl-smi">NgModule</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
  <span class="pl-c1">providers</span>: <span class="pl-kos">[</span>
    <span class="pl-kos">{</span> 
      <span class="pl-c1">provide</span>: <span class="pl-smi">HydrateDomConfig</span><span class="pl-kos">,</span> 
      <span class="pl-c1">useValue</span>: <span class="pl-kos">{</span>
        <span class="pl-c1">hydrate</span>: <span class="pl-c1">true</span><span class="pl-kos">,</span> <span class="pl-c">// default false for backwards compat</span>
        <span class="pl-c1">attribute</span>: <span class="pl-s">'pid'</span><span class="pl-kos">,</span> <span class="pl-c">// default 'id'</span>
      <span class="pl-kos">}</span> 
    <span class="pl-kos">}</span>
  <span class="pl-kos">]</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">Component:</p>
<div class="highlight highlight-source-ts notranslate position-relative overflow-auto" dir="auto" data-snippet-clipboard-copy-content="@Component({
  template: `
    &lt;div pid=&quot;header&quot;&gt;
      &lt;header-ad pid=&quot;header-ad&quot;&gt;&lt;/header-ad&gt;
      &lt;div&gt;
        &lt;!-- this will get blown away and re-created since it lacks attribute --&gt;
      &lt;/div&gt;
    &lt;/div&gt;
  `
})"><pre class="notranslate">@<span class="pl-smi">Component</span><span class="pl-kos">(</span><span class="pl-kos">{</span>
  <span class="pl-c1">template</span>: <span class="pl-s">`</span>
<span class="pl-s">    &lt;div pid="header"&gt;</span>
<span class="pl-s">      &lt;header-ad pid="header-ad"&gt;&lt;/header-ad&gt;</span>
<span class="pl-s">      &lt;div&gt;</span>
<span class="pl-s">        &lt;!-- this will get blown away and re-created since it lacks attribute --&gt;</span>
<span class="pl-s">      &lt;/div&gt;</span>
<span class="pl-s">    &lt;/div&gt;</span>
<span class="pl-s">  `</span>
<span class="pl-kos">}</span><span class="pl-kos">)</span></pre></div>
<p dir="auto">This design allows the <code class="notranslate">DomRenderer</code> 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.</p>
<p dir="auto">Text nodes would all be destroyed and re-created with this design, as well as any node that doesn't have the set attribute, <code class="notranslate">pid</code>.</p>
<p dir="auto">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.</p>
<p dir="auto">CC <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/gdi2290/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/gdi2290">@gdi2290</a> <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/pxwise/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/pxwise">@pxwise</a></p>
<p dir="auto"><a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/tbosch/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/tbosch">@tbosch</a> &amp; <a class="user-mention notranslate" data-hovercard-type="user" data-hovercard-url="/users/alxhub/hovercard" data-octo-click="hovercard-link-click" data-octo-dimensions="link_type:self" href="https://github.com/alxhub">@alxhub</a> please feel free to add anything I missed (or misrepresented).</p>