AngularJS and Yii2 Part 1: Routing

AngularJS is becoming more and more popular which makes it a valuable skill for both – frontend and backend web developers.

growing-popularity-of-angularjs-google-trends

In this tutorial we will build an AngularJS web app with Yii framework 2.0 handling the backend. We will take the Yii2 advanced template as the base and Angularize it.

The first part of this tutorial will deal with navigation and partial views. This is how our final result will look. You can download the code from GitHub.

Assets

First install Yii advanced template using composer, or download it as an archive, extract it and run the init command. Now we need to get AngularJS. Since Yii2 uses composer we just need to add three more lines to the “require” section in composer.json.

"bower-asset/angular": "*",
"bower-asset/angular-route": "*",
"bower-asset/angular-strap": "*"

And run php composer.phar update.

Now let’s make an AssetBundle so we could easily serve Angular js files from the layout.

Here’s how our frontend/assets/AngularAsset.php will look:

<?php
namespace frontend\assets;

use yii\web\AssetBundle;
use yii\web\View;

class AngularAsset extends AssetBundle
{
    public $sourcePath = '@bower';
    public $js = [
        'angular/angular.js',
        'angular-route/angular-route.js',
        'angular-strap/dist/angular-strap.js',
    ];
    public $jsOptions = [
        'position' => View::POS_HEAD,
    ];
}

public $jsOptions = [ ‘position’ => View::POS_HEAD, ];  will tell Yii to include the JavaScript files from the <head> section of our layout instead of the very end of the <body> section so AngularJS would load as soon as possible.

We will also have to add it as dependency to ‘frontend/assets/AngularAsset.php’. And tell Yii2 to serve the code of our Angular app ‘js/app.js’.

class AppAsset extends AssetBundle
{
    public $basePath = '@webroot';
    public $baseUrl = '@web';
    public $css = [
        'css/site.css',
    ];
    public $js = [
        'js/app.js',
    ];
    public $depends = [
        'yii\web\YiiAsset',
        'yii\bootstrap\BootstrapAsset',
        'frontend\assets\AngularAsset',
    ];
}

Layout and Partials

We will serve the layout using Yii and the partial views will be loaded directly from the server.

serving-content

That’s why we will mostly work with frontend/views/layouts/main.php. We will have to convert all of the views from frontend/views/ to plain HTML and move them to frontend/web/partials/.

Our frontend/controllers/SiteController.php will be very simple because we will delegate some of the MVC responsibilities to AngularJS.

<?php
namespace frontend\controllers;

use Yii;
use yii\web\Controller;

/**
 * Site controller
 */
class SiteController extends Controller
{
    /**
     * @inheritdoc
     */
    public function actions()
    {
        return [
            'error' => [
                'class' => 'yii\web\ErrorAction',
            ],
            'captcha' => [
                'class' => 'yii\captcha\CaptchaAction',
                'fixedVerifyCode' => YII_ENV_TEST ? 'testme' : null,
            ],
        ];
    }

    public function actionIndex()
    {
        return $this->renderContent(null);
    }
}

Note that we use return $this->renderContent(null); instead of  return $this->render(‘index’);. This makes Yii2 render the layout without a view.

AngularJS

The layout

Let’s start by adding the ng-app directive to the <html> element. This will  auto-bootstrap our AngularJS application that we are going to call “app”.

<html lang=”<?= Yii::$app->language ?>” ng-app=”app”>

We won’t be using php views so let’s type in a static title.

<title>My Angular Yii Application</title>

There is a great tool for tracking the page load progress called Pace. It will become helpful when we’ll get to form submission. We will configure it to show a progress bar on top of the page for GET and POST requests. This code will go into the <head> section.

<script>paceOptions = {ajax: {trackMethods: ['GET', 'POST']}};</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/pace/1.0.2/pace.js"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/pace/1.0.2/themes/red/pace-theme-minimal.css" rel="stylesheet" />

We won’t be using Yii2 bootstrap widgets, so we’ll have to make our own bootstrap navbar. The bs-navbar directive in the nav tag is provided by AngularStrap and it will automatically assign the active css class to the menu item that corresponds with the current route. We only need provide the route information for every menu item using this directive data-match-route=”/about”

To make our navbar collapsable on smaller screens we will add several directives to the collapse button. ng-init=”navCollapsed = true” to initialize it as collapsed. ng-click=”navCollapsed = !navCollapsed” to toggle the collapse state when the button is clicked. And two more to the div that contains the menu items. ng-class=”!navCollapsed && ‘in'”  to toggle the in css class depending on the value of the navCollapsed variable. And ng-click=”navCollapsed=true”  to collapse the navbar once a menu item is clicked. This is important because normally the page is reloaded once a menu item is clicked, but this isn’t the case with an Angular app.

<nav class="navbar-inverse navbar-fixed-top navbar" role="navigation"  bs-navbar>
    <div class="container">
        <div class="navbar-header">
            <button ng-init="navCollapsed = true" ng-click="navCollapsed = !navCollapsed" type="button" class="navbar-toggle">
                <span class="sr-only">Toggle navigation</span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span>
                <span class="icon-bar"></span></button>
            <a class="navbar-brand" href="#/">My Company</a>
        </div>
        <div ng-class="!navCollapsed && 'in'" ng-click="navCollapsed=true" class="collapse navbar-collapse" >
            <ul class="navbar-nav navbar-right nav">
                <li data-match-route="/$">
                    <a href="#/">Home</a>
                </li>
                <li data-match-route="/about">
                    <a href="#/about">About</a>
                </li>
                <li data-match-route="/contact">
                    <a href="#/contact">Contact</a>
                </li>
                <li data-match-route="/login">
                    <a href="#/login">Login</a>
                </li>
            </ul>
        </div>
    </div>
</nav>

The ng-view directive tells AngularJS where to render the partial views.
<div ng-view></div>

The app

The code for our AngularJS app will be located at frontend/web/js/app.js. First we will define our app and include the modules that we are going to use.

var app = angular.module('app', [
    'ngRoute',      //$routeProvider
    'mgcrea.ngStrap'//bs-navbar, data-match-route directives
]);

ngRoute is provided by angular-route.js. We require it for $routeProvider, which we will use later in our configuration. mgcrea.ngStrap is provided by angular-strap.js. We need it for our bootstrap navbar.

app.config(['$routeProvider',
    function($routeProvider) {
        $routeProvider.
            when('/', {
                templateUrl: 'partials/index.html'
            }).
            when('/about', {
                templateUrl: 'partials/about.html'
            }).
            when('/contact', {
                templateUrl: 'partials/contact.html'
            }).
            when('/login', {
                templateUrl: 'partials/login.html'
            }).
            otherwise({
                templateUrl: 'partials/404.html'
            });
    }
]);

This configuration contains our routing information including paths to our partial views. It’s fairly simple now, but we will add to it later on.

Learn about authentication in Part 2.

Published by

Alexander

Your friendly, neighborhood, full stack, pseudorandom text generator

24 thoughts on “AngularJS and Yii2 Part 1: Routing”

  1. Hi I’n mew in Yii2 and I have changes the directory structure. So I have moved frontend/web files into a public_html folder. The problem is that Angular is not loadin any of the html views (I am also new in Angular… -_-). Please help me!

      1. Yes! I can access the html files. Firebug tells me those errors:

        GET http://localhost/pdaw/public_html/debug/default/toolbar?tag=555462bd8431d
        404 Not Found

        GET http://localhost/pdaw/public_html/debug/default/toolbar?tag=555462bd8431d
        404 Not Found

        Error: [ng:areq] Argument ‘MainController’ is not a function, got undefined
        http://errors.angularjs.org/1.3.15/ng/areq?p0=MainController&p1=not%20a%20function%2C%20got%20undefined
        minErr/<@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:63:12
        assertArg@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1587:1
        assertArgFn@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1597:1
        $ControllerProvider/this.$get</<@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:8470:9
        nodeLinkFn/<@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:7638:34
        forEach@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:331:11
        nodeLinkFn@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:7625:11
        compositeLinkFn@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:7117:13
        compositeLinkFn@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:7120:13
        publicLinkFn@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:6996:30
        bootstrapApply/<@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1457:11
        $RootScopeProvider/this.$get</Scope.prototype.$eval@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:14466:16
        $RootScopeProvider/this.$get</Scope.prototype.$apply@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:14565:18
        bootstrapApply@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1455:9
        invoke@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:4203:14
        bootstrap/doBootstrap@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1453:1
        bootstrap@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1473:1
        angularInit@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:1367:5
        @http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:26304:5
        trigger@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:2762:7
        createEventHandler/eventHandler@http://localhost/pdaw/public_html/assets/eb3ff9a6/angular/angular.js:3032:9

        but next it tells me that It loaded correctly index file?

        GET http://localhost/pdaw/public_html/partials/index.html
        304 Not Modified

        1. Looks like the problem is in the js code. I can’t help you without seeing the code, but you can look for the solution here. It’s not an uncommon problem. You are probably overwriting one of your variables.

  2. Im really concerned about the little security i can provide for my app. Im saying this because i ll not be using php views any longer, or will render using
    $this->renderAjax(“view”)
    And sometimes, i like to do a little validation on the view, such as using the :

    if (Yii::$app->session->hasFlash)
    and other minute check. These codes are solely known by the server.

    My problem is although js will work fine the same way, js codes are presented to the browser.
    And the user can reverse engineer my codes. Isnt it kinda security doubting???

    1. I had similar concerns when I started learning AngularJS. If you design your app correctly, there won’t be any vulnerabilities. If you’re concerned about exposing your views, presentation logic and/or buisness logic – you can rely more on the backend and less on Angular. In fact, you might not even need Angular. Sometimes jQuery is more than enough to get the job done.

      AngularJS is great when you want to delegate some of the work from the server to the client machine. Less traffic, faster responses from the server.

      You can also use the REST capabilities that Yii2 offers to make an API for a native mobile app.

      1. I think this is great….It all depends on the architecture.
        I hate it when i look like a pure newbie… 🙂

  3. Hi,

    I’m facing this problem

    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular/angular.js
    angular.js
    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular-route/angular-route.js
    angular-route.js
    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular-strap/dist/angular-strap.js
    angular-strap.js
    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular/angular.js
    angular.js
    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular-route/angular-route.js
    angular-route.js
    “NetworkError: 404 Not Found – http://ad/frontend/web/assets/34ab7c57/angular-strap/dist/angular-strap.js
    angular-strap.js

    ReferenceError: angular is not defined
    var app = angular.module(‘app’, [

  4. It gives me the error ‘angular is not defined’. I tried to locate what would be the cause of the problem, but I think that maybe the Angular is not set correctly. I looked my AppAsset and AngularAsset and their are exactly the same you’re using. What would be causing that?

  5. Hi,
    Firstly I would like to thank you for this wonderful blog as I followed It I was easily able to setup angularjs in my application frontend.

    however, I am a bit stuck at where you did:

    public function actionIndex()
    {
    return $this->renderContent(null);
    }

    Now In my action I was also sending out user data along with the file to render like following:

    public function actionIndex()
    {
    return $this->render(‘index’,[‘userName’=>$userName,’data’=>$data]);
    }

    How will I be able to send the additional data ? Will I have to make an ajax call after render to get the data? or something else?

    Please help me get the best way to do that. Thanks again for the information 🙂

Leave a Reply