Skip to content
This repository was archived by the owner on Oct 14, 2024. It is now read-only.

Re-rendering when bootstraping at client #139

Closed
bhkim526 opened this issue Aug 3, 2016 · 44 comments
Closed

Re-rendering when bootstraping at client #139

bhkim526 opened this issue Aug 3, 2016 · 44 comments

Comments

@bhkim526
Copy link

bhkim526 commented Aug 3, 2016

Using async http is causing the UI flickering when bootstraping at client side since the UI is rendered twice, 1 at server-side and 1 at client-side after each async http call.

So I came up with a home-brewed service which embeds the data from the first http call at server into rendered html so I can skip the second call and use the cached data instead at client side but still the UI is re-rendered and flickers due to the client-side bootstraping.

Is there any way I can skip the second rendering? or am I doing this in wrong way? or there is any better way to do this? should I use precache, primeCache or prime? there is no example or sample I can find on these options though.

@felipedrumond
Copy link

I'm having the same issue here. I believe that there should exist a strategy that allows us to configure what is going to run or re-run at the client or not. For instance, if the client has a cart-id cookie, I'd like to re-run a http call to get its cart json and update the view, without bootstraping the whole app at the client.

@MarkPieszak
Copy link
Contributor

The flicker is something we can look at, there might be something new going on that's causing it. As for the client bootstrapping, it actually needs to render again, since that's how the browser turns it into an actual SPA application, otherwise it's nothing more than rendered html.

An ideal way to store http calls from the server and use that cached data is still in the works. Is that what you guys mean?

@abeleshko
Copy link

You can see this flicker on the screenshot
screen shot 2016-08-09 at 9 36 05 am

@kuldeepkeshwar
Copy link
Contributor

flickering duration increases as ajax calls on page increases

@kuldeepkeshwar
Copy link
Contributor

duplicate #102

@kuldeepkeshwar
Copy link
Contributor

kuldeepkeshwar commented Sep 14, 2016

@gdi2290 @jeffwhelpley @MarkPieszak
this is still happening with rc7 release
1.

screen shot 2016-09-14 at 2 45 38 pm

screen shot 2016-09-14 at 2 45 57 pm

screen shot 2016-09-14 at 2 46 09 pm

@jeffwhelpley
Copy link

We did resolve this issue with the latest updates. The only reason why you would still see this is if preboot.complete() is called before the client side http calls are complete. Just so you understand what is going on here. With the new changes, the server view will be displayed until the client view is completely rendered. The switch between the hidden client view and displayed server view only occurs when the client view is complete. So, if you see a flicker it means the client view is not actually complete and doesn't render until after the switch. This would only happen if there is some http call completed AFTER preboot is completed.

@kuldeepkeshwar, is your code public? Alternatively ping me on g chat tomorrow and I can screen share with you.

@kuldeepkeshwar
Copy link
Contributor

kuldeepkeshwar commented Sep 14, 2016

@jeffwhelpley got your point(I didn't use preboot.complete(),although I have passed preboot options preboot: { appRoot: ['app'], uglify: true } ). but earlier prebootComplete was part of universal module.
and if I directly use prebootClient from preboot module, then I need to generate inlinePrebootCode and inject in the HEAD section of the server-side template.

Ideally, if preboot option is turned on ,universal should out of box generate inlinePrebootCode and inject it in the server-side template.

Code:
I have cloned the universal-starter and the only change I have made is in App component.

import { Component } from '@angular/core';

@Component({
  selector: 'app',
  styles:[require('./app.css')],
  template: 'Hello Universal App <span class="bg">{{test}}</span>'
})
export class App {
private test:string='test';
  constructor(){
    setTimeout(()=>this.test='async',100)
  }
}

 @NgModule({
    bootstrap: [ App ],
    declarations: [ App ],
    imports: [
      UniversalModule.withConfig({
        document: config.document,
        originUrl: 'http://localhost:3000',
        baseUrl: '/',
        requestUrl: '/',
        // preboot: false,
        preboot: { appRoot: ['app'], uglify: true },
      }),
      FormsModule
    ]
  })

@PatrickJS
Copy link
Contributor

the latest version has preboot via flag in the express config and the client universal module checks for preboot so don't use UniversalModule.withConfig as it's an advanced feature now

@kuldeepkeshwar
Copy link
Contributor

@jeffwhelpley @gdi2290
I have turned on the preboot flag, but still, the view is flickering.

repo: https://github.com/kuldeepkeshwar/universal-starter

@jeffwhelpley
Copy link

OK, I will try out your code tonight and let you know.

@jeffwhelpley
Copy link

@kuldeepkeshwar not sure if I am doing this the right way, but I just ran your project and I didn't see any flicker. Can you tell me the exact steps to take to recreate the issue (assume that i have the project running and am on the default page of the app).

@kuldeepkeshwar
Copy link
Contributor

kuldeepkeshwar commented Sep 22, 2016

@jeffwhelpley use localhost:3000/offers/delhi-ncr or navigate to local route

@jeffwhelpley
Copy link

@kuldeepkeshwar I need to investigate this a bit further, but I don't think this has anything to do with preboot. It looks like the flash specifically is coming from the snippet behind your router outlet. I need to see why there is the delay in resolving that route because it should resolve immediately on the client, but if you look at the network tab, you can see that the client side route doesn't resolve right away.

@kuldeepkeshwar
Copy link
Contributor

@jeffwhelpley API call does take time (intentionally made it wait for 1 second to make it look like more real time), but preboot should show server rendered view until client bootstraps completely

@jeffwhelpley
Copy link

Wait..why are you doing the http call in the provider? I am not sure that will work that way. If you move it to the ngOnInit() I would guess this problem will go away.

@kuldeepkeshwar
Copy link
Contributor

@jeffwhelpley I have to put the http call in the HomeResolve(Resolve) because I want data to be available in the component .
If I make call from ngOnInit() then this will again cause blinking effect until the http call completes

@emanuelverardi
Copy link

emanuelverardi commented Sep 23, 2016

Hi! I'm having the same Issue.
I'm using angular2 universal with server rendering and I'm showing a list of videos that came from my API.
As you can see, if is the first time the list of videos are being shown and after a second it desapears and another http request is made and the list of videos appear again.
This is too bad for UX.
I tried to optimize it using ng2-cache to cache my http request data on my session storage. But in the first time that the page is rendered (when I have no cache) the problem still happen.
Any Idea ?

Live Example of my problem:
http://www.primetube.com

@mekya
Copy link

mekya commented Oct 1, 2016

Hi,
I'm also having the same issue and it delays project timeline. I can share full source code if you need.

Bests,
A. Oguz

@MarkPieszak
Copy link
Contributor

@jeffwhelpley you think this is a preboot issue or just a universal browser platform bootstrap issue that's causing this? We can always do some CSS trick and overlay the client boo trapped version hidden yet on top of the server rendered, then hide the server one and show the other real fast, that might work? Forget what it's doing now.

@kuldeepkeshwar
Copy link
Contributor

@MarkPieszak @jeffwhelpley

We can always do some CSS trick and overlay the client boo trapped version hidden yet on top of the server rendered, then hide the server one and show the other real fast, that might work

is this what preboot does(apart from capturing the event and replaying them) ?

@jeffwhelpley
Copy link

There should not be any flicker. This is a bug. I will be investigating this later today and give more details about the root cause.

@kuldeepkeshwar
Copy link
Contributor

@jeffwhelpley you can use this repo.
I will be online , let me know if I can help you

@jeffwhelpley
Copy link

OK, so I think we identified the root cause here. Basically, preboot.complete() is running on the client side before all the http calls complete. So, what is happening is:

  1. Server view displayed
  2. Client view generated in hidden div but http calls not complete
  3. Preboot switches views
  4. Client view now displayed without data
  5. Http calls complete and then do update

That is the momentary flash that you all see. There are two ways to fix this. First, we can delay preboot.complete() until all http calls complete. That could be tricky to implement and would only delay further the switch to the client view. The alternative is to get the NgCache working so that the data from the server side is stored in the server view and then used by the client instead of making the same http call again.

Patrick is working on the NgCache solution now, but just note that this will not be perfect for all use cases since it requires stuffing data in the server view (i.e. and thus if the data is huge it could increase the time to download the server view). So, the workaround for right now is to change AUTO_PREBOOT to false in your providers (i.e. so preboot complete() is not called automatically):

https://github.com/angular/universal/blob/aa4fc4239a6177ab4c70bc8c6af35b84148bbebb/modules/universal/src/browser/universal-module.ts#L45

and then manually call prebootClient.complete() once your client side http calls have resolved:

https://github.com/angular/universal/blob/aa4fc4239a6177ab4c70bc8c6af35b84148bbebb/modules/universal/src/browser/universal-module.ts#L91

We will update this issue once there is progress either for NgCache or for something to make it easier to control preboot complete().

@felipedrumond
Copy link

Hi @jeffwhelpley. If I simply delay the preboot by 10 seconds (just to make sure all my requests will be completed), it will blink anyway in the following scenario: angular/universal#514 (comment)

@kuldeepkeshwar
Copy link
Contributor

@jeffwhelpley AUTO_PREBOOT is not present in version 2.0.11 .
This was added by Allow for configuring if preboot complete() is called…

I guess We need to bump the version

@jeffwhelpley
Copy link

Ah, yes. Our mistake. Patrick is about to push in another change and he will publish the new version after that goes in.

@ignaciolarranaga
Copy link

ignaciolarranaga commented Oct 17, 2016

Hi @jeffwhelpley, quick question. I'm following your suggestion regarding AUTO_PREBOOT false and manually call the prebootComplete, but I think I'm having some rookie mistake because I'm receiving this exception:

TypeError: angular2_universal_1.prebootComplete is not a function

Version: 2.1.0-rc.1

What I did is:

  • Set AUTO_PREBOOT to false:
...
import { AUTO_PREBOOT } from "angular2-universal";

let providers: any[] = [
    {
      provide: AUTO_PREBOOT,
      useValue: false
    }
];
providers.push(appProviders);

@NgModule({
    bootstrap: [ App ],
    declarations: appDeclarations,
    providers: providers,
    imports: [
        UniversalModule, // BrowserModule, HttpModule, and JsonpModule are included
        FormsModule,
        RouterModule.forRoot(appRoutes)
    ]
})
export class MainModule {}
  • And this after the http loading is complete:
...
import { prebootComplete } from "angular2-universal";
...
export class TrimsListingComponent ....
     ngOnInit() {
        this._xxxService.getData...(...)
            .subscribe(
                (response: ResponseTrims) => {
                    this.trims = response.trims;

                    prebootComplete();
                },
                error => console.log("Error: " + error)
            );
    }

@jeffwhelpley
Copy link

Try this:

var preboot = window.prebootClient();
preboot.complete();

@ignaciolarranaga
Copy link

Thanks @jeffwhelpley, actually it did not work:

EXCEPTION: window is not defined
ORIGINAL STACKTRACE:
ReferenceError: window is not defined

but a similar approach does:

declare var preboot;
...
    if (typeof preboot !== 'undefined') {
        preboot.complete();
    }

One naive question. Why not to put the preboot.complete() at the end of the async queue by default ?, this will force all the async events to happen before the preboot gets executed and no need to disable it and do it manually, or am I missing something ?

@jeffwhelpley
Copy link

Which async queue are you referring to? You can initiate async events anywhere in your code. You either need to manually coordinate them all through a custom mechanism (in which case you would have to manually call preboot.complete() OR you can modify NgZone so that upon initial page load, you want for the app to be stable and then call automatically call preboot.complete(). The stabilization strategy is something utilized by Protractor for testing, but it is not perfect and we are still discussing the best way to implement something similar which would work for everyone.

@ignaciolarranaga
Copy link

Well I was thinking on using the zone as you mentioned. If I got it correctly you can catch all the async calls (with the addAsyncListener I guess) on the client inside the zone (as you did on the server) and executing the preboot.complete() after all of them finish, and before the new async events from the client starts.
Or perhaps even better, you can just wait till the same async events that were catch on the server rendering are completed.
Because, If I'm getting it correctly the page received from the server is supposed to be on the same state right ?

I mean, it is like prebooting after all the "pre-render" async events got executed, but I might be missing something fundamental :)

@jeffwhelpley
Copy link

jeffwhelpley commented Oct 17, 2016

No, the "stable" state on the server and client are fundamentally different. They are similar but generally speaking the client "stable" state can take longer because there may be multiple async calls and push based realtime which is involved. On the server, the rule we have is just 1 iteration of async before we return the response. The client "stable" state is more complicated. But it is certainly possible and something we will explore in the future.

All that said, yes, I suggest you try out your own zone implementation and play with it. If you get it working well, you should think about submitting a PR.

@ignaciolarranaga
Copy link

Thanks for the answer, let me adjust also the way to manually execute the preboot (the previous was actually never executing :)).
It would be:

import { prebootComplete } from "angular2-universal";
...
ngOnInit() {
    ....
    ...initialization code...
    ....
    if (typeof prebootComplete != 'undefined') {
        prebootComplete();
    }
}

And some kind of flag to not execute preboot again if the component is re-initialized.

In the server will be undefined, and in the client will be defined (that was actually the original problem that I had "TypeError: angular2_universal_1.prebootComplete is not a function"

@jeffwhelpley
Copy link

so...as long as you have the preboot client in your client side package (which should be the case if you are including universal), then there should be a global preboot object on the window that you can use to call preboot.complete().

@ignaciolarranaga
Copy link

Exactly, the protection is for the server, because prebootComplete is not defined in the server (I think that was the reason for the "TypeError: angular2_universal_1.prebootComplete is not a function" that I received originally, I didn't recall it was when the component is rendered on the server).

@jeffwhelpley
Copy link

Yes, preboot on the server is only used to insert the inline JavaScript into the page. You never call complete on the server side.

@PatrickJS
Copy link
Contributor

Here's an example of how to transfer a request made on the server to the client to prime the client cache
https://gist.github.com/gdi2290/1852053c7e629c9ea3720415c381ecd3

@martin-mueller-solutions

@gdi2290 Well done! This saved my weekend. Thanks bro!

@PatrickJS
Copy link
Contributor

I added the cache to the repo

@Skillnter
Copy link

Any update on flickering issue? The flickering persist...

@J4cku
Copy link

J4cku commented Oct 17, 2017

Same for me :(

@jeffwhelpley
Copy link

@J4cku @Skillnter there are two strategies to eliminate flickering now:

  1. Re-use data from server side. This will be part of the Angular 5 release, but you can use the latest beta release in your own project if you want to try and get a head start.
  2. Utilize Preboot like this example of Preboot with the Universal Starter.

Also note that the universal team is looking into what it would take to hydrate the client app instead of re-rendering. This is not a straightforward process and lots of hurdles to overcome, but is a potential for the future.

@Skillnter
Copy link

@jeffwhelpley update for angular 5 is in universal repo but i still see the glitch. I think they have preboot integrated. I am lil' lost here... I know its not a straightforward process and lots of hurdles to overcome. I just want to know for now if its been implemented. I was working on 1st strategy... so i am not sure if i have to continue my work or not.

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests