Some time ago Angular2 was released. I was excited because this framework is a breath of freshness in the frontend world. To start developing new apps using Angular2 a quick start created. A small problem with it is it may be hard to integrate the whole stack in an existing application. I decided to write a quick tutorial how to run Angular2 on Symfony application.

Requirements and dependencies

We need to have npm installed. How to install npm you can read in the docs. NodeJS applications can download its dependencies using package.json file. We will use it, too. Additionaly, we need to install the assets as absolute symbolic links. To do so type

./bin/console assets:install --symlink

We need to do it because the original files we keep in src/YourBundle/Resources/public directory. It is to make sure we will not need install the assets everytime we make changes.

Configuration

To make our life easier I will use the quick start project where we have configured all we need. What you need is to download it to the web folder. The package.json should be in location web/package.json. If you use git to download the project, remember to remove ./web/.git folder.

cd web
git clone https://github.com/angular/quickstart
mv -f quickstart/* .
mv -f quickstart/.* .

First of all, we need to clean it up a bit. Remove app and quickstart folder, index.html and styles.css files.

rm -rf app index.html styles.css quickstart/

We removed the app folder because we will use the Symfony’s bundles folder where we keep all the assets anyway. The next step is updating package.json files to fit our needs. In the lint command we need to update the folder where ts files are kept. Change it to the ‘bundles’. We do not need the lite server, because we will use the default php built-in server (of course for development). The updated config file is below

{
  "name": "angular-quickstart",
  "version": "1.0.0",
  "description": "QuickStart package.json from the documentation, supplemented with testing support",
  "scripts": {
    "e2e": "tsc && concurrently \"protractor protractor.config.js\" --kill-others --success first",
    "lint": "tslint ./app/**/*.ts -t verbose",
    "pree2e": "webdriver-manager update",
    "test": "tsc && concurrently \"tsc -w\" \"karma start karma.conf.js\"",
    "test-once": "tsc && karma start karma.conf.js --single-run",
    "tsc": "tsc",
    "tsc:w": "tsc -w"
  },
  "keywords": [],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@angular/common": "~2.2.0",
    "@angular/compiler": "~2.2.0",
    "@angular/core": "~2.2.0",
    "@angular/forms": "~2.2.0",
    "@angular/http": "~2.2.0",
    "@angular/platform-browser": "~2.2.0",
    "@angular/platform-browser-dynamic": "~2.2.0",
    "@angular/router": "~3.2.0",
 
    "angular-in-memory-web-api": "~0.1.15",
    "systemjs": "0.19.40",
    "core-js": "^2.4.1",
    "reflect-metadata": "^0.1.8",
    "rxjs": "5.0.0-beta.12",
    "zone.js": "^0.6.26"
  },
  "devDependencies": {
    "concurrently": "^3.1.0",
    "typescript": "^2.0.10",
 
    "canonical-path": "0.0.2",
    "http-server": "^0.9.0",
    "tslint": "^3.15.1",
    "lodash": "^4.16.4",
    "jasmine-core": "~2.4.1",
    "karma": "^1.3.0",
    "karma-chrome-launcher": "^2.0.0",
    "karma-cli": "^1.0.1",
    "karma-htmlfile-reporter": "^0.3.4",
    "karma-jasmine": "^1.0.2",
    "karma-jasmine-html-reporter": "^0.2.2",
    "protractor": "4.0.9",
    "webdriver-manager": "10.2.5",
    "rimraf": "^2.5.4",
 
    "@types/node": "^6.0.46",
    "@types/jasmine": "^2.5.36",
    "@types/selenium-webdriver": "^2.53.33"
  },
  "repository": {}
}

The another file we need to update is systemjs.config.js file. We need to make two changes. First of all, replace the app folder to the new one (line no 14). Secondly, the path to the main.js file is changed and it will not be in bundles/main.js location, but in bundles/app/ts/main.js. Remember Symfony copies (in our case adds symlinks) fro src/SomeBunde/Resources/public folder to web/bundles/some folder. In my example, I have an AppBundle that’s why the path looks how it looks. It may be confising that’s why I want to stres it. Here is the file after my tweaks.

/**
 * System configuration for Angular samples
 * Adjust as necessary for your application needs.
 */
(function (global) {
  System.config({
    paths: {
      // paths serve as alias
      'npm:': 'node_modules/'
    },
    // map tells the System loader where to look for things
    map: {
      // our app is within the app folder
      app: 'bundles',
 
      // angular bundles
      '@angular/core': 'npm:@angular/core/bundles/core.umd.js',
      '@angular/common': 'npm:@angular/common/bundles/common.umd.js',
      '@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
      '@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
      '@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
      '@angular/http': 'npm:@angular/http/bundles/http.umd.js',
      '@angular/router': 'npm:@angular/router/bundles/router.umd.js',
      '@angular/router/upgrade': 'npm:@angular/router/bundles/router-upgrade.umd.js',
      '@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
      '@angular/upgrade': 'npm:@angular/upgrade/bundles/upgrade.umd.js',
      '@angular/upgrade/static': 'npm:@angular/upgrade/bundles/upgrade-static.umd.js',
 
      // other libraries
      'rxjs':                      'npm:rxjs',
      'angular-in-memory-web-api': 'npm:angular-in-memory-web-api/bundles/in-memory-web-api.umd.js'
    },
    // packages tells the System loader how to load when no filename and/or no extension
    packages: {
      app: {
        main: './app/ts/main.js',
        defaultExtension: 'js'
      },
      rxjs: {
        defaultExtension: 'js'
      }
    }
  });
})(this);

Now it is time to install the NodeJS dependencies. Nothing can be simpler!

cd web
npm install

Developing the first component

The SystemJS expects the main.ts file in web/bundles/app/ts folder. It is time to create it!

// src/AppBundle/Resources/public/ts/main.ts
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './app.module';
 
const platform = platformBrowserDynamic();
platform.bootstrapModule(AppModule);

The only thing we do here is loading the main module. We must add it, too.

// src/AppBundle/Resources/public/ts/main.ts
import { NgModule }      from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { AppComponent }   from './app.component';
 
@NgModule({
    imports:      [ BrowserModule ],
    declarations: [ AppComponent ],
    bootstrap:    [ AppComponent ]
})
 
export class AppModule { }

This file is very simple, too. It just adds and lunches the AppComponent. In this component, we will answer a very important question: Does Symfony loves Angular?

// src/AppBundle/Resources/public/ts/main.ts
import { Component } from '@angular/core';
 
@Component({
    selector: 'symfony-loves-angular',
    template: 'Yes, it does!'
})
 
export class AppComponent { }

We are almost done. Our Angular application should be working, but it is not loaded from Symfony. To do so we need to update the base.html.twig template to load all necessary libraries and our app.

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8" />
        <title>{% block title %}Welcome!{% endblock %}</title>
        {% block stylesheets %}{% endblock %}
        <link rel="icon" type="image/x-icon" href="{{ asset('favicon.ico') }}" />
 
        <!-- 1. Load libraries -->
        <!-- Polyfill(s) for older browsers -->
        <script src="{{ asset('node_modules/core-js/client/shim.min.js') }}"></script>
        <script src="{{ asset('node_modules/zone.js/dist/zone.js') }}"></script>
        <script src="{{ asset('node_modules/reflect-metadata/Reflect.js') }}"></script>
        <script src="{{ asset('node_modules/systemjs/dist/system.src.js') }}"></script>
        <!-- 2. Configure SystemJS -->
        <script src="{{ asset('systemjs.config.js') }}"></script>
        <script>
            System.import('app').catch(function(err){ console.error(err); });
        </script>
    </head>
    <body>
        {% block body %}
        {% endblock %}
        {% block javascripts %}{% endblock %}
    </body>

In app/Resources/views/default/index.html.twig template we will ask the most important question today and check if it will answer.

{% extends 'base.html.twig' %}
 
{% block body %}
    Does Symfony loves Angular2?
    <symfony-loves-angular>Let me think...</symfony-loves-angular>
{% endblock %}

The last thing left is to compile the ts files. In the web folder type

npm run tsc:w

It will compile the files and wait for all the changes. It will automatically apply them so we can develop without thinking about recompiling. In another console run built in web server by typing

./bin/console server:start

and open page http://127.0.0.1:8000/ in your browser. That’s it! We can clearly see that Symfony is in love with Angular! I do not know what about you, but I am touched.

Summary

As you can see it is quite easy to use both Symfony and Angular in one project. If you want to see the final version, I added the final project to github. You may experience some error logs similar to

../src/AppBundle/Resources/public/ts/app.component.ts(2,27): error TS2307: Cannot find module '@angular/core'.
../src/AppBundle/Resources/public/ts/app.module.ts(2,31): error TS2307: Cannot find module '@angular/core'.
../src/AppBundle/Resources/public/ts/app.module.ts(3,31): error TS2307: Cannot find module '@angular/platform-browser'

while compiling the TypeScript from the command line. It happens because NodeJS gets the absolute path of files. If you install the assets with prod environment, it will not happen because in this case, it’ll copy the files – not add symlink which causes the problem. For now, I have no idea how to solve it. Maybe you have the idea? 🙂

Anyway, I hope I helped you and you enjoyed this post. Cheers!

About the author

Bartłomiej Kiełbasa

Bartłomiej Kiełbasa

Hi! I'm Bartek. I'm a PHP developer but other languages are not scary to me. My hobby is security and I try to learn as much as it's possible how to not be hacked. I'd ike to know how things work. After hours I like playing Dota2 and watching Dragon Ball :) If you will have any question, feel free to ask.