Skip to content

Instantly share code, notes, and snippets.

@BallisticPain
Last active February 5, 2019 16:46
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save BallisticPain/4ab1fb5a6ce22981ee6b0ac20ccc5516 to your computer and use it in GitHub Desktop.
Save BallisticPain/4ab1fb5a6ce22981ee6b0ac20ccc5516 to your computer and use it in GitHub Desktop.
Angular 2+ Webpack, AoT, Symfony, Twig, PHP Integration - Single Page Application within Monolith

I built the Angular application completely outside in another repository from the Symfony backend PHP project. I used a seed project: https://github.com/AngularClass/angular2-webpack-starter

Currently as we intend to replace the functionality of the Frontend Form Areas (heavy jQuery / JavaScript)

  1. We are building each section at the very least as an NgModule (hopefully all in one app). a. We are looking into the HMR (Hot Module Replacement) of the seed project as a way whereby we can have all of the NgModules powering the frontend just getting deployed to their respective areas.
  2. / route must point directly to the compenent you want your Symfony page to display in Angular.

Using the seed project tasks (we aren't currently using AoT, but the seed can) we build the code for copy/paste into Symfony. npm run build:prod

Next we need to copy the dist files that we built in the previous step... cp *.js ../your/project/web/assets/hotels-ng/

Now we need to move over to the Symfony side of the project. We have hopefully locally tested the application with it outside of Symfony (much easier development this way). You will need at least the NelmioCORSBundle or handle CORS requests yourself for development unless you have both the Angular and Symfony on the same local domain name. I just used npm run server so that was simply an IP Address:Port... nothing fancy. I also had to comment out the security.yml black listing all routes for login.

So inside the above dist folder we had our index.html file. This has a few important pieces of code we need... Namely, we need (I found out after some testing)... <base href="/"> => <base href="{{ app.request.basePath }}"> Keep in mind, it will add a / using the Angular Router to the Symfony path. This is an issue to workout. Hash (#) routing might be better for an embedded Angular route, or teaching symfony how to deal with an extra slash on Angular routes.

The <script> tags before the <body> tag as those three files are your Angular application compiled and the same three files we copied with the cp *.js from above.

You can take note of the CSS and JavaScript files I have in the index.html as those will either need to be in a base template, or otherwise accessible through Symfony / Twig. I do end up needing two of them in my angular-hotels.html.twig template as they aren't in base.

So you can see in the angular-hotels.html.twig what I have brought over as far as specifically for this area to now become an SPA (Single-Page Application). Currently, I'm not using the user's uuid, but passed it in in-case I need to later as I thought Authorizing the Frontend Application would be needing to be done as an API with a token. Background: We have sealed ALL but a few white listed routes from requiring a login so we can be sure on our "endpoints" (the Symfony API points are a rough start to an API) that we have a user to save reservations to, and show only their list of reservations and so on.

For completeness, I have added the controller action here so you can see no magic happening. HotelsController.php

Here are some takeaways for me... Angular / Symfony / Twig / Community

  1. I was extremely surprised and pleased with how little effort actually needed to be made for Symfony to spit an Angular app to the frontend. From what I had read, I needed to change all my {{ }} to {[{ }]} (Angular) or {{ twig_variable | raw }} (Symfony).
  2. I was happy to not need to build the full API Token authorization and test it as well with this push. I re-wrote the Twig/JavaScript version of our Hotel Search / Reservation in about 8 weeks.
  3. There is very little documentation/tutorials of people bringing Symfony as the backend and Angular as the frontend. With the latest releases of both (2+ or 4+ and 3+ respectively).
  4. Didn't end up needing the NelmioCORSBundle (handles cross-site scripting) outside of development as once I put it inside the Twig template, it was then being hosted on the same domain and box.
  5. I was able to fix some bugs that were present in the Twig version of this section of our application simply by moving over to Angular. I let a framework handle the stuff that should be common.

Overall very pleased with the experience, and will continue to do it this way until they truly are an API backend and a frontend.

Always open to PR's and Feedback on my Gist(s).

@BallisticPain (Twitter, Github)

Vortex Revolutions LLC / upCode L.L.C.

{% extends '::base.html.twig' %}
{% block head_javascript %}
{{ parent() }}
<base href="{{ app.request.basePath }}">
<script src="{{ asset('assets/hotel-ng/polyfills.f5a8d946c189980b5b4b.bundle.js') }}" type="text/javascript" defer></script>
<script src="{{ asset('assets/hotel-ng/vendor.ef3a505ff252d831f7e3.bundle.js') }}" type="text/javascript" defer></script>
<script src="{{ asset('assets/hotel-ng/main.76d8ad076062b2c1ae60.bundle.js') }}" type="text/javascript" defer></script>
{% endblock %}
{% block main %}
<app data-user-uuid="{{ app.user.uuid }}">
Loading...
</app>
{% endblock %}
{% block javascripts %}
<script src="{{ asset('assets/vendor/card/dist/jquery.card.js') }}"></script>
<script src="{{ asset('assets/vendor/accounting.js/accounting.min.js') }}"></script>
{% endblock %}
<?php
namespace AppBundle\Controller;
// use statements
class HotelsController extends FOSRestController
{
/**
* @Route("/hotels/search", name="airnav_hotels_search", methods={"GET"}, options={"expose"=true})
* @param Request $request
* @return Response
*/
public function angularSearchAction(Request $request)
{
return $this->render(':reservation/hotel:angular-search.html.twig');
}
}
// Not using anything special from FOSRestController being extended there.
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta http-equiv="x-ua-compatible" content="ie=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Hotel Frontend Angular</title>
<meta name="description" content="Hotel Frontend Angular">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css" integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.7.0/css/font-awesome.min.css">
<link rel="stylesheet" href="assets/ppicker/jquery.periodpicker.min.css">
<link rel="stylesheet" href="assets/select2/css/select2.min.css">
<link rel="stylesheet" href="assets/select2/css/select2-bootstrap.min.css">
<link rel="stylesheet" href="assets/pages/css/pages-icons.css">
<link rel="stylesheet" href="assets/pages/css/pages.min.css">
<!-- Configured Head Tags -->
<link rel="apple-touch-icon" sizes="57x57" href="/assets/icon/apple-icon-57x57.png">
<link rel="apple-touch-icon" sizes="60x60" href="/assets/icon/apple-icon-60x60.png">
<link rel="apple-touch-icon" sizes="72x72" href="/assets/icon/apple-icon-72x72.png">
<link rel="apple-touch-icon" sizes="76x76" href="/assets/icon/apple-icon-76x76.png">
<link rel="apple-touch-icon" sizes="114x114" href="/assets/icon/apple-icon-114x114.png">
<link rel="apple-touch-icon" sizes="120x120" href="/assets/icon/apple-icon-120x120.png">
<link rel="apple-touch-icon" sizes="144x144" href="/assets/icon/apple-icon-144x144.png">
<link rel="apple-touch-icon" sizes="152x152" href="/assets/icon/apple-icon-152x152.png">
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icon/apple-icon-180x180.png">
<link rel="icon" type="image/png" sizes="192x192" href="/assets/icon/android-icon-192x192.png">
<link rel="icon" type="image/png" sizes="32x32" href="/assets/icon/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="96x96" href="/assets/icon/favicon-96x96.png">
<link rel="icon" type="image/png" sizes="16x16" href="/assets/icon/favicon-16x16.png">
<link rel="manifest" href="/assets/manifest.json">
<meta name="msapplication-TileColor" content="#00bcd4">
<meta name="msapplication-TileImage" content="/assets/icon/ms-icon-144x144.png">
<meta name="theme-color" content="#00bcd4">
<!-- base url -->
<base href="/">
<script src="polyfills.f5a8d946c189980b5b4b.bundle.js" type="text/javascript" defer></script><script src="vendor.ef3a505ff252d831f7e3.bundle.js" type="text/javascript" defer></script><script src="main.76d8ad076062b2c1ae60.bundle.js" type="text/javascript" defer></script></head>
<body>
<app>
Loading...
</app>
<!-- Google Analytics: change UA-71073175-1 to be your site's ID -->
<!--<script>-->
<!--(function(A,n,g,u,l,a,r){A.GoogleAnalyticsObject=r;A[l]=A[l]||function(){-->
<!--(A[l].q=A[l].q||[]).push(arguments)},A[l].l=1*new Date();a=n.createElement(g),-->
<!--r=n.getElementsByTagName(g)[0];a.async=1;a.src=u;r.parentNode.insertBefore(a,r)-->
<!--})(window,document,'script','https://www.google-analytics.com/analytics.js','ga');-->
<!--ga('create', 'UA-71073175-1', 'auto');-->
<!--ga('send', 'pageview');-->
<!--</script>-->
<script src="https://code.jquery.com/jquery-3.1.1.min.js" integrity="sha256-hVVnYaiADRTO2PzUGmuLJr8BLUSjGIZsDYGmIJLv2b8=" crossorigin="anonymous" type="text/javascript" defer></script>
<script src="assets/ppicker/jquery.periodpicker.full.min.js" type="text/javascript" defer></script>
<script src="assets/select2/js/select2.full.min.js" type="text/javascript" defer></script>
<script src="assets/moment/moment.min.js" type="text/javascript" defer></script>
<script src="assets/card/jquery.card.js" type="text/javascript" defer></script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment