GrabDuck

AngularJS and Yii2 Part 1: Routing - Neat Tutorials Blog

:

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.