3

I'm getting started on Angular 2 after much success with Angular 1. I followed both the Quickstart and the Tour of Heroes tutorials and everything works like a charm.

The lite server gets kicked off, I see tsc running in watch mode and I even see BrowserSync is hooked up. Great!

However, I need to start making things a bit more real world.

Instead of using lite server, how do I get all this working using a flask dev or gunicorn server, serving the initial index.html file as a rendered jinja template?

Giving the flask dev server a very näive try, I basically copy the contents of the example index.html from the tutorial into my jinja template, then run npm run tsc:w and finally fire up my flask dev server and hope for the best. Things compile fine. But in the browser I see problems:

angular2-polyfills.js:332 Error: SyntaxError: Unexpected token <
    at ZoneDelegate.invoke (http://127.0.0.1:5000/static/node_modules/angular2/bundles/angular2-polyfills.js:332:29)
    at Zone.run (http://127.0.0.1:5000/static/node_modules/angular2/bundles/angular2-polyfills.js:227:44)
    at http://127.0.0.1:5000/static/node_modules/angular2/bundles/angular2-polyfills.js:576:58
Evaluating http://127.0.0.1:5000/app/main.js
Error loading http://127.0.0.1:5000/app/main.js`

Looking at the culprit transpile main.js file I see:

(function(System, SystemJS, require) {<!doctype html>
<html>
<head lang='en'>

So, yeah, that's not gonna work...clearly my wiring is haywire.

There is a lot of black magic going on with the shims, the polyfills, reactive extensions, systemjs, angular2 itself, and then toss in tsc and lite server. Admittedly, I don't have all this down yet and it will take time, but I'm hoping to get my project into a sane state fairly quickly.

(I don't mind using lite server (BrowserSync is a nice perk) in development as long as I can configure it to proxy the real flask server which will return the render jinja templates.)

Update

Here is the actual template index file with some minor changes that I've made:

<!doctype html>
<html>
  <head lang="en">
    {% block head %}

    <meta charset="utf-8">

    <title>Angular 2 QuickStart</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">    
    <link rel="stylesheet" href="styles.css">
    <!-- 1. Load libraries -->
    <!-- IE required polyfills, in this exact order -->
    <script src="node_modules/es6-shim/es6-shim.min.js"></script>
    <script src="node_modules/systemjs/dist/system-polyfills.js"></script>
    <script src="node_modules/angular2/es6/dev/src/testing/shims_for_IE.js"></script>   
    <script src="node_modules/angular2/bundles/angular2-polyfills.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>
    <script src="node_modules/rxjs/bundles/Rx.js"></script>
    <script src="node_modules/angular2/bundles/angular2.dev.js"></script>
    <!-- 2. Configure SystemJS -->
    <script>
      System.config({
        packages: {        
          app: {
            format: 'register',
            defaultExtension: 'js'
          }
        }
      });
      System.import('app/main')
            .then(null, console.error.bind(console));
    </script>

    {% endblock %}
  </head>
  <!-- 3. Display the application -->
  <body>
    {% block content %}{% endblock %}

    <my-app>Loading...</my-app>

    <script>
      (function(globals) {
        this.MyConfig = {
          staticDir: '{{ config["STATIC_DIR"] }}'
        };
      }(this));
    </script>
  </body>
</html>
lostdorje
  • 6,150
  • 9
  • 44
  • 86
  • You better to provide a source of your template. – Alexander Trakhimenok Apr 04 '16 at 10:11
  • sure thing. good point...i added the template code to the question. – lostdorje Apr 04 '16 at 10:44
  • I'ts weird you have the HTML in the main.js. It should be in the output of "http://localhost:NNN/" or whatever you use for serving the document. I guess it can be linked with the reactive extensions (is it RreactiveJS?). Try to disable them? – Alexander Trakhimenok Apr 04 '16 at 11:11
  • right it is weird. if i just comment out the System.config() and System.import() code the html gets served just like you'd expect, and of course nothing happens. The main.js file containing the HTML -- I don't really know why, nor how to debug it. This file is just some ethereal file that doesn't really exist anywhere on the file system so I'm not sure how to debug how it's being generated. – lostdorje Apr 04 '16 at 13:13

2 Answers2

8

I had the same problem and here is how I solved it;

Step 1

With a directory structure as follows:

+- MyAppName
+-- ServerApp
+--- //...flask files here
+-- ClientApp
+--- node-modules
+--- app
+--- //...more node+angular application files

I exposed the ClientApp folder in my flask application at the URL .../client-app/... using the following code:

from flask import Flask, send_from_directory
import os

BASE_URL = os.path.abspath(os.path.dirname(__file__))
CLIENT_APP_FOLDER = os.path.join(BASE_URL, "ClientApp")

# This is required by zone.js as it need to access the
# "main.js" file in the "ClientApp\app" folder which it
# does by accessing "<your-site-path>/app/main.js"
@app.route('/app/<path:filename>')
def client_app_app_folder(filename):
    return send_from_directory(os.path.join(CLIENT_APP_FOLDER, "app"), filename)

# Custom static data
@app.route('/client-app/<path:filename>')
def client_app_folder(filename):
    return send_from_directory(CLIENT_APP_FOLDER, filename)

Step 2

Head over to your index.html file (I placed mine at ServerApp\templates\index.html so that I could simply do render_template('index.html')) and make it look something like this:

<html>
  <head>
    <title>Angular 2 QuickStart</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="stylesheet" href="client-app/assets/css/style.css">

    <!-- 1. Load libraries -->
     <!-- Polyfill(s) for older browsers -->
    <script src="client-app/node_modules/es6-shim/es6-shim.min.js"></script>

    <script src="client-app/node_modules/zone.js/dist/zone.js"></script>
    <script src="client-app/node_modules/reflect-metadata/Reflect.js"></script>
    <script src="client-app/node_modules/systemjs/dist/system.src.js"></script>

    <!-- 2. Configure SystemJS -->
    <script src="client-app/systemjs.config.js"></script>
    <script src=""></script>
    <script>
      System.import('app').catch(function(err){ console.error(err); });
    </script>
  </head>

  <!-- 3. Display the application -->
  <body>
    <my-app>Loading...</my-app>
  </body>
</html>

the 'client-app' prepended to the paths is the route I chose to expose my client_app_folder() function at

Step 3

Configure your client application's package finder to use the set route ('client-app/...' in this case). I use system.js and therefore I made my systemjs.config.js file look like this:

(function(global) {

  // map tells the System loader where to look for things
  var map = {
    'app':                        'client-app/app', // 'dist',
    'rxjs':                       'client-app/node_modules/rxjs',
    'angular2-in-memory-web-api': 'client-app/node_modules/angular2-in-memory-web-api',
    '@angular':                   'client-app/node_modules/@angular'
  };

  // packages tells the System loader how to load when no filename and/or no extension
  var packages = {
    'app':                        { main: 'main.js',  defaultExtension: 'js' },
    'rxjs':                       { defaultExtension: 'js' },
    'angular2-in-memory-web-api': { defaultExtension: 'js' },
  };

  var packageNames = [
    '@angular/common',
    '@angular/compiler',
    '@angular/core',
    '@angular/http',
    '@angular/platform-browser',
    '@angular/platform-browser-dynamic',
    '@angular/router',
    '@angular/router-deprecated',
    '@angular/testing',
    '@angular/upgrade',
  ];

  // add package entries for angular packages in the form '@angular/common': { main: 'index.js', defaultExtension: 'js' }
  packageNames.forEach(function(pkgName) {
    packages[pkgName] = { main: 'index.js', defaultExtension: 'js' };
  });

  var config = {
    map: map,
    packages: packages
  }

  // filterSystemConfig - index.html's chance to modify config before we register it.
  if (global.filterSystemConfig) { global.filterSystemConfig(config); }

  System.config(config);

})(this);

I only modified the map variable

Godspeed!

Archy
  • 280
  • 3
  • 13
  • 1
    Thanks for the detailed answer! Setting up the route '/app/...' for zone.js was a big key for me. Now I just need to find the time to go back and get hooked in to this and get my favorite 3rd party Angular 1 directives wired in as well. – lostdorje May 22 '16 at 07:17
1

When looking for the answer to this same question I was reminded about changing the interpolation characters in angular 1. Could find where two do that for angular 2, but it was simple enough to change on the jinja2 side. Set the jinja2 environment options characters however you want with this:

JINJA_ENV = jinja2.Environment(
  loader=jinja2.FileSystemLoader(os.path.dirname(__file__)),
  extensions=['jinja2.ext.autoescape'],
  block_start_string= '{[%',
  block_end_string='%]}',
  variable_start_string='{[',
  variable_end_string=']}',
  comment_start_string='{#',
  comment_end_string='#}',
  autoescape=True
)

Hopefully I am using compatible strings here, guess I will find out! (see http://jinja.pocoo.org/docs/dev/api/ environment options)

A. Pearce
  • 38
  • 6