My experience with Angular 4 and server-side rendering using platform-server



Let me first state that I’m a professional Angular 1.x developer. I know Angular inside and out, and have fully grasped many of the complexities within Angular’s framework. However, I have little to no experience with Angular 2+, so I decided to try the latest Angular 4 code with robwormald’s ng-universal demo. The intent was to create a simple website with server-side rendering and a couple of minor AJAX service requests.

It seems like everyone remembers Angular JS 1.x and while it was complex, once you learned “The Angular Way”™ it more or less fell into place. The problem was it was only a client-side application so you would have to download all of the Javascript, plus the templates which were usually held in a Javascript file, then wait for Angular to start up and interpret the HTML, then inject all of the directives, services, controllers, filters, and process all of the variables on the page with watchers, which took a few milliseconds (or more depending on how complex they became) before displaying anything on the page. Beyond that you had AJAX requests that you had to make before you could fully see a complete website.

We would add spinners and loading graphics to keep people from leaving, but hardcore developers hated the size of the files they had to download and wanted instant gratification.

React and other frameworks started to implement server-side rendering, and Angular 2 was extended with a module called angular-universal. With Angular Universal you could render the initial HTML server-side, handling the HTTP requests on the NodeJS server and presenting the raw HTML to the browser, then the client-side code would take over. This provides quick rendering and presentation while providing the Single Page App functionality once everything was loaded.

Challenges

Webpack

My first challenge was accepting Webpack. I’m a big fan of Gulp and have little experience with Webpack. To me it’s a little bit between Grunt and Gulp with its JSON configuration. It does allow plugins but, as with Grunt, it uses a bit more configuration magic that I’m not accustomed to.

Grunt was a JSON nightmare for large projects. I moved away from it because I couldn’t trust that my team of mostly junior developers could modify the configuration without breaking something, and I had a difficult time myself holding the entire configuration in my head (a skill I have). This is when I decided to switch to Gulp.
tt
Gulp, to me, felt like a blank canvas where the developer became the artist and could paint the build and deployment process however he or she wanted. It was pure Javascript and the tasks were small functions that any Javascript developer could understand. I felt confident that I could give this code to a junior developer and have them implement a new task, and I often did just that.

Webpack, in my opinion, is between Grunt and Gulp. I feel like there’s a lot of magic that happens with the JSON configuration, but since it does allow plugins and customization it keeps the configuration compact enough to understand in a reasonable time. A lot of people are using it, but I feel like it’s another tool fad that people will move away from, perhaps eventually hailing the greatness of Brunch next year.

The problem for me was that Angular 4 uses Typescript and I couldn’t get Gulp to deal with the Typescript files without errors. Disgruntled, I relented to using Webpack, but I swear to find an eventual solution to my initial problem.

Typescript

Typescript is touted as a “Superset of Javascript!” While this might be true, as a Javascript developer who’s been developing websites with Javascript since the 1990s, and professionally since 2000, I found Typescript to be confusing at times.

I know Java, PHP, Perl, Python, ASP.Net, C#, ASP classic, some C++, a bit of Assembly, so changing between languages isn’t a difficult task for me. The difficult part with Typescript is the type definition for things like constructors and the confusing “Observable” which is an object and it represents an asynchronous object, not a string.

Take the code below as an example:

import { Component, OnInit } from '@angular/core';
import { TransferHttp } from '../../../modules/transfer-http/transfer-http';
import { Observable } from 'rxjs/Observable';

@Component({
  selector: 'home-view',
  templateUrl: '../../templates/home/home-view.component.html'
})
export class HomeView implements OnInit {
  public things: Observable<string>;

  constructor(private http: TransferHttp) {}

  ngOnInit() {
    this.things = this.http.get('/search').map(data => {
      return data.things;
    });
  }
}

In Angular 1 this.things would have automatically earned a watcher that would update the page when the AJAX request completed, but with Angular 4 you have to assign it to an Observable, but instead of the Observable being of type <Array>, since that’s what “things” represents, it uses <string>. Maybe I’m oblivious as to why <string> is used, but it’s not intuitive to myself so I would have to take some time to research the implementation details.

In Angular 1 this.http represented a promise, so things like this.http.get(...).then() just worked, but not anymore. Now this.http is an Observable so you have to request the promise from the Observable, but in order to do that you might have to change your import statement from import { Observable } from 'rxjs/Observable'; to import { Observable } from 'rxjs/Rx';. This isn’t at all intuitive unless you start digging through obscure documentation, Stock Overflow questions, or the rxjs code. How about we just import the whole mess and use Intellisense to figure it out while we’re coding? Okay, that was rhetorical because we don’t want all of the code, but there’s still a little frustration that this fragmentation creates a black box for the new developers.

Server Side Rendering Nightmares

Remember our this.http.get code from above? This won’t work on the server-side, because platform-server (different than platform-browser remember) wants all HTTP requests to use absolute URLs, not relative URLs. This is understandable because when the server-side code gets the request it doesn’t actually know which server, port, and protocol to use. It doesn’t know that it’s running on http://localhost:3000, or that it’s running a server at all. For all the server-side knows, you might be running the API on a completely different server.

This might be reasonable thinking, but in my frustration I wonder why the platform-server developers can’t just get the request object when the URL is relative and use the same protocol, host, and port. I mean all of this data is in the request. No, they want you to use Zone.js instead with the code Zone.current.get('req') || {}. The || {} is a safety measure because this code would also be running in the client and the client doesn’t have a request object.

With Zone we are supposed to get the request object, pull the host from that and put it in the URL for the this.http.get() call. But, the catch is that we don’t have a request object in the client side so we also need to get the host from the client. For this we would use window.location, but the server side code doesn’t have a window object so that’s why we need Zone.js.

The problem, and this one made me want to write this article, is that Zone.js doesn’t work with Angular 4, so there’s no request object and we can’t get the URL for the server-side requests. I’m not a huge fan of hard-coding absolute URLs in my HTTP calls that would need to work across multiple environments.

Conclusion

Maybe I’ll go back to Ember, or Vue.js sounds nice. I need to look at pictures of puppies playing with flowers now.

Honestly, I need a lot more experience with Angular 4 to make a good judgement, but the way that the frameworks are going I also need to expand my experience to React and Vue.js, among others.

Leave a Reply

Your email address will not be published. Required fields are marked *