TrilonIO / aspnetcore-angular-universal

ASP.NET Core & Angular Universal advanced starter - PWA w/ server-side rendering for SEO, Bootstrap, i18n internationalization, TypeScript, unit testing, WebAPI REST setup, SignalR, Swagger docs, and more! By @TrilonIO
https://www.trilon.io
MIT License
1.46k stars 434 forks source link

Angular 4.x integration #83

Closed MarkPieszak closed 7 years ago

MarkPieszak commented 7 years ago

Some notes:

Approximate boot-server API will be:

platform
  .bootstrapModuleFactory(MyAppModuleFactory)
  .then((ref: ApplicationRef) => ref.isStable.filter(v => v).toPromise())
  .then(() => {
     return { 
       html: platform.injector.get(PlatformState).renderToString()),
       meta: platform.injector.get(MetaServce)
     };
  } 
  .then(result => /* do magic */ )
  .then(() => platform.destroy());

Then we'll integrate it with .NET to render the Title & Meta tags in the MVC view template.

This is also connected to #10

kukjevov commented 7 years ago

Any news here? Are you planning to update this for beta.8 or will you wait for rc.0 ng4 ?

Thank you for great job here :-)

MarkPieszak commented 7 years ago

I'll be updating very shortly currently testing it out to make sure and give feedback to the team, I'll make a PR to the repo in the next day or two!

When it gets in let me know if any issues pop up for you! @kukjevov

kukjevov commented 7 years ago

Hello :).

I can see that you are already working on it, it is just great :). I want to ask you are there any changes required from https://github.com/aspnet/JavaScriptServices project to make angular4 working? Are you planning to update also their template for Angular2Spa, or you are going in different direction as they are going ?

Thank you :)

MarkPieszak commented 7 years ago

So far everything is going well! I'm currently creating a wrapper for the .NET integration so it will automatically try to pass down Title/Meta & Styles tags separately (for you to parse yourself on the .NET side).

Yes I was going to send a PR to the JavaScriptServices repo as well! Some main differences will be that we'll no longer use the TagHelper, and that the code will either be triggered in the Controller Action, and passed down via ViewData, or in the .cshtml file itself.

I'll message you as soon as I get a more polished PR to get in here so you can test it out and give me any feedback! Sorry for the delay, I'm making sure to give any feedback back to the team quickly so everything works flawlessly for .NET as well.

Cheers

hheexx commented 7 years ago

Hi Mark!

I know you are redoing angular-asp integration now so I wanted to ask if we can make a way that if angular router can't find a route that it send's that info to asp so it can set http status code to 404.

Important for SEO.

MarkPieszak commented 7 years ago

Hey @hheexx So I'm going to be gone this next week so I can't imagine I'll be able to put anything up just yet (I'd rather make sure everything is ironed out and totally production ready!) It shouldn't be an issue to have both AoT and ng 4.x though no worries :)

As for the 404, if you refresh the page here: It actually already does Render the 404 on the server through the Angular app: http://aspnetcore-angular2-universal.azurewebsites.net/not-found

You want .NET to handle that instead?

AbrarJahin commented 7 years ago

I think that is not possible to handle 404 page by the server because angular works after server HTTP done and it works on the pages what is not existing on the server.

So, 404 page should be handled by angular as far as I know.

if you @hheexx need to use a server rendered 404 page then you should use that inside angular 404 page with a service, as far as I know.

Isn't it right @MarkPieszak ?

hheexx commented 7 years ago

Mark, you can see in developer tools. 404 page you shared is still served with http status code 200. https://i.gyazo.com/ce381a772a4ca7b47e1f1dc0c760dc7f.png That is not good for SEO. Google does not understand it's 404 page.

It is ok that 404s are served from angular but we just need a way for a angular to pass right status code to asp with rendered string so asp can set correct status code.

hheexx commented 7 years ago

How it's going @MarkPieszak ?

Just to remind you, it's really burning under my feet xD

dguisinger commented 7 years ago

Hey @MarkPieszak any comments on where this is at? I saw you commented in your last ng4 branch checkin that you were shelving changes for now.

Any timeline for when things will be ready? ng4 release should be almost any day now.

Also, I hate to be that person, but any chance you can write a walkthrough on how to convert a project that was built from the version included in JavascriptServices back in November. I've slowly been working through all of the changes, but things are vastly different and not really a drop-in replacement if you already built a project off of the old code.

MarkPieszak commented 7 years ago

Glad you asked I'm actually working on it now :)

Using the latest RC5 that was released a second ago today 👍 screenshot below image


I'm actually trying to mirror more closely to Steve's JavaScriptServices to make life easier for people, there have been some recent big changes to the App structure but they seem to be causing more problems than helping, so I'm going back to a more simpler structure :)

I'll do the best I can do document an upgrade strategy as well! @dguisinger @hheexx @kukjevov

Expect something in here by the latest Sunday. 4.0 official release is next week 💯

dguisinger commented 7 years ago

Great, looking forward to it.

Any idea how to solve this? I had your code up and running, and I can't figure out what I changed but now I'm getting: NodeInvocationException: The Node invocation timed out after 60000ms. You can change the timeout duration by setting the InvocationTimeoutMilliseconds property on NodeServicesOptions.

I kept increasing the timeout, but it seems to top out at 60 seconds even if I put 120 seconds as the setting. If I turn off server side rendering things work just fine. The lack of visibility into what is going on here is quite frustrating, I've been stuck on this all day.

MarkPieszak commented 7 years ago

What code are you trying to run? The code in the ng4 branch is very old, I have everything locally win the latest stuff 😓 I'll push up the updated code today though so you guys can take a look. One of the last remaining issues is animations breaking on the server but Vikram and Alex are working on that one.

I'll keep you posted today as I get stuff in here.

dguisinger commented 7 years ago

hah yeah, I've been using your 2 week old code... I think part of it may be just the size of the project, my modules aren't lazy loading at the moment. I don't suppose you've been working to add AoT compiling support to your web pack example that's included, without it you can't lazy load modules when webpack is used. I miss the days of everything being built right into the visual studio build chain, you have so many tools in this template I don't fully understand yet

MarkPieszak commented 7 years ago

Yeah the whole ecosystem / build tools / etc have changed pretty dramatically the past 2-3 years. Lazy-loading will work fine with the latest stuff no worries there! :)

MarkPieszak commented 7 years ago

image

So it's basically all set, I'm just cleaning up the Repo in the morning, I think it's best to simplify it again a little bit (and take it back to the 1 project way it was before).

In the screenshot above we have everything being abstracted out automatically, and those things are all rendered by .NET. :)

Sorry about the delay, 4.0 release should be Wednesday, just wanted to iron everything out beforehand.

@dguisinger @hheexx @kukjevov

hheexx commented 7 years ago

Hi @MarkPieszak,

Looking good :)

Can you test/confirm following use cases:

1) cannonical url <link rel="canonical" href="https://blog.example.com/dresses/green-dresses-are-awesome" />

2) hraflang <link rel="alternate" hreflang="es" href="http://es.example.com/" />

3) right http status code comment 9

MarkPieszak commented 7 years ago

As for canonical, I'll provide a nice LinkService class that'll automatically grab them (if you have any link info setup in your router : data {}

image

Is canonical and that alterante something that is changed per Route or a global thing? You'll be able to just make some Meta or Link elements default if they never change.

hheexx commented 7 years ago

per route. alterante should link url-s in different languages. It should be connected to i18n systems.

kukjevov commented 7 years ago

Hi @MarkPieszak,

i have been working on my own solution for angular 4.0 SSR and everything around it, since we are using angular in both Java and .Net environment in our company. I had great inspiration from you and from FrozenPandaz ng-universal-demo. Thanks to both of you i have working solution so far. You can check it https://github.com/kukjevov/ng-universal-demo/tree/mine if you want, maybe have some notes, suggestions for me :). You will not be able to run it since i am using some packages which are not in public npm repository.

But still i have managed to enable also preboot. You can quite easily switch among, compilation with aot, ssr, prod, hmr.

But why am i writing :). I need your advice. How to make XhrRequest working on Server side if there is some authentication (Windows Authentication, Cookies authentication, Basic Http Authentication), since all of these require either user interaction or presence of cookies which are provided by browser automatically?

And another thing is that i took different approach to rendering SSR from asp.net core. Since i am using Asp.Net Core MVC only as WebApi i did not want to use any cshtml. I was thinking about Middleware that will directly call same code (with slight modifications) as i have in my server.js

        if(!isServerRenderAvailable())
        {
            next();

            return;
        }

        getServerRenderFunc()(path.join(__dirname, wwwroot, 'index.html'), req.originalUrl, {baseUrl: "http://localhost:8888/"}, function(err, succ)
        {
            res.setHeader('Content-Type', 'text/html');

            res.end(err || succ);
        });

in repository above which will only render everything in string and return it to Asp.Net Core. But now i am fighting with thought if it is not better to run whole server.js on different port and proxy requests to this server? Why am i thinking about this solution? Because it would be easier to process cookies on Angular SSR, since you can use https://www.npmjs.com/package/cookies on both solutions with Asp.Net Core or directly only with Connect server. And maybe some other advantages could be there.

What do you think about this? Thank you for any help :).

hheexx commented 7 years ago

Hi @MarkPieszak,

Angular 4 is released and this is sliding day by day for a month now. Can we get any hard date when we can expect this to land or if you can land anything on a breach now.

If we can't get this in production by end of next week we have to switch to prerender.io or something similar.

MarkPieszak commented 7 years ago

Unfortunately the changes in both VS2017 & Angular 4.0 made things keep getting pushed back due to new and different errors...

I'm about to push up a WIP one, (The styles/meta/title are commented out, as the ASPnetEngine still needs a few tweaks, it'll also be on NPM when it's finished, it's inside the project at the moment) as of 4.0 release last night but they were with RC6) But that shouldn't be too tough to fix. I just figure you want something so you can get going at least.

When I fix the aspnetcoreEngine it'll fill in Title/Meta/Link/Style etc automatically, but it has everything else in place for it to work once the engine is done. .NET will handle them all now once it gets them parsed back.

At least now it's VS2017, and simpler as well. I'll add more features in there soon, just want to keep it a little simpler than the way the repo got :)

MarkPieszak commented 7 years ago

https://github.com/MarkPieszak/aspnetcore-angular2-universal/tree/angular4.0-NEW-wip

hheexx commented 7 years ago

Thanks a lot Mark! I feel much better when I can work on something then when I sit helpless.

I have started integration. Had a problem with this bug https://github.com/angular/angular/issues/11580

I managed to fix it with seperate plugin config for client and server:

Client: new webpack.ContextReplacementPlugin(/angular(\\|\/)core(\\|\/)@angular/, path.resolve(__dirname, 'notexist/')),

new webpack.ContextReplacementPlugin(/\@angular\b.*\b(bundles|linker)/, path.join(__dirname, 'notexist/')),

I don't really understand what I done, it was trail and error.

hheexx commented 7 years ago

Also, do we need this in packages "@angular/tsc-wrapped": "^0.5.0", ? As I understand it's sub dependency of compiler.

MarkPieszak commented 7 years ago

Probably don't need it, it'll be included anyway I believe. Yeah those just change trailing lines during the webpack config, no worries. As long as it works 💃

hheexx commented 7 years ago

I'm currently stuck with very ungrateful error:

Exception: Call to Node module failed with error: Prerendering failed because of error: undefined

Any idea?

btw AOT is not in yet?

hheexx commented 7 years ago

Solved. reflact-metadata was missing.

hheexx commented 7 years ago

Mark,

What is a way to use isPlatformBrowser ?

1)

if (isPlatformBrowser) {
}

2)

if (isPlatformBrowser()){
}

or 3)

 constructor(@Inject(PLATFORM_ID) platformId: string) {
    if(isPlatformBrowser(platformId)){
}
  }

I'm pretty sure it's third option but then I get: Error: Uncaught (in promise): Error: No provider for PLATFORM_ID!

MarkPieszak commented 7 years ago

You're using it like so?

import { isPlatformBrowser } from '@angular/common';
import { PLATFORM_ID } from '@angular/core';

export class SomeComponent implements OnInit {

    private isBrowser: boolean = isPlatformBrowser(this.platform_id);`

    constructor(@Inject(PLATFORM_ID) private platform_id) {}

    ngOnInit() { // example usage, this could be anywhere in this Component of course
      if (this.isBrowser) { 
          alert('we're in the browser!');
      }
    }

You could also just create your own that would work sort of like the isBrowser we used to have in angular2-universal, just need to inject them like so instead @Inject('isBrowser') private isBrowser: boolean

// app.browser
@NgModule({
  providers: [
    { provide: 'isBrowser', useValue: true }
  ]
})
dguisinger commented 7 years ago

I applied your changes to my existing project and am having an issue with image references.

I'm assuming this has something to do with the webpack config changes but I am not that knowledgeable in configuring webpack; i compared your source to my old source and nothing stood out.

I'm getting the following: Module not found: Error: Can't resolve './img/avatar-2.jpg' Which is a client-side reference to wwwroot/img/avatar-2.jpg coming from Any idea what changed? With newer versions of everything should I be referencing static content differently?

The stack trace is referencing: @ multi webpack-hot-middlewear/client?path=%@F__webpack_hmr ./Client/boot-client.ts

hheexx commented 7 years ago

Yea @MarkPieszak. Like so.

Now I switched to your solution with isBrowser provider and it's much more elegant and more importantly - it's working. Thanks!

LiverpoolOwen commented 7 years ago
 ERROR in ./~/browserify-sign/algos.js
    Module not found: Error: Can't resolve './browser/algorithms' in 'C:\Users\o
wen\Source\Repos\Angular4Core\src\Angular2Spa\node_modules\browserify-sig
n'
     @ ./~/browserify-sign/algos.js 1:17-48
     @ ./~/crypto-browserify/index.js
     @ ./~/angular2-universal-polyfills/~/reflect-metadata/Reflect.js
     @ ./~/angular2-universal-polyfills/browser.js
     @ dll vendor

I am seeing this when running webpack on the WIP branch

hheexx commented 7 years ago

@LiverpoolOwen I just had same error on build server.

Try deleting node_modules. That fixed it for me.

LiverpoolOwen commented 7 years ago

Thanks @hheexx! I just ran an rm -rf node-modules and ran npm i then webpack and webpack --config webpack.config.vendor.js and it seems to be fine.

Although when I ran rm -rf node-modules and 'yarn' it tried running the webpack configs and hit the error again. Weird.

MarkPieszak commented 7 years ago

Yarn might not be triggering the postinstall script?

LiverpoolOwen commented 7 years ago

Yes that will be it thanks. Any update on the AOT? @MarkPieszak

hheexx commented 7 years ago

@MarkPieszak

In aspnetcore-engine.ts, is there any particular reason why you get meta and title from AST_DOCUMENT but you get style by cutting output html string?

There is a bug currently that meta tags are emitted twice because they are in html string placed in between multiple