angular / universal-starter

Angular Universal starter kit by @AngularClass
2.02k stars 688 forks source link

Re-rendering when bootstraping at client #139

Closed bhkim526 closed 8 years ago

bhkim526 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

kuldeepkeshwar commented 8 years ago

flickering duration increases as ajax calls on page increases

kuldeepkeshwar commented 8 years ago

duplicate #102

kuldeepkeshwar commented 8 years ago

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

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

2.

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

3.

screen shot 2016-09-14 at 2 46 09 pm
jeffwhelpley commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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

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

jeffwhelpley commented 8 years ago

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

jeffwhelpley commented 8 years ago

@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 commented 8 years ago

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

jeffwhelpley commented 8 years ago

@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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

@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 commented 8 years ago

@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 commented 8 years ago

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 commented 8 years ago

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

jeffwhelpley commented 8 years ago

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 commented 8 years ago

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: https://github.com/angular/universal/issues/514#issuecomment-251217047

kuldeepkeshwar commented 8 years ago

@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 commented 8 years ago

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

ignaciolarranaga commented 8 years ago

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:

...
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 {}
...
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 commented 8 years ago

Try this:

var preboot = window.prebootClient();
preboot.complete();
ignaciolarranaga commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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_universal1.prebootComplete is not a function"

jeffwhelpley commented 8 years ago

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 commented 8 years ago

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_universal1.prebootComplete is not a function" that I received originally, I didn't recall it was when the component is rendered on the server).

jeffwhelpley commented 8 years ago

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 commented 8 years ago

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 commented 8 years ago

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

PatrickJS commented 8 years ago

I added the cache to the repo

Skillnter commented 7 years ago

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

J4cku commented 7 years ago

Same for me :(

jeffwhelpley commented 7 years ago

@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 commented 6 years ago

@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.