Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

How to Use Yii2 RESTful API Interface With Angular

DZone's Guide to

How to Use Yii2 RESTful API Interface With Angular

In this post, we're provided a tutorial that shows how to connect the AngularJS framework with the Yii2 RESTful API. Check it out!

· Web Dev Zone
Free Resource

Prove impact and reduce risk when rolling out new features. Optimizely Full Stack helps you experiment in any application.

Hi there, readers of DZone! In this article, I would like to continue my series of articles about large project development with Yii2 (article 1 and article 2). But this time I want to focus on the concrete usage of AngularJS, which is bundled with our application developed on the Yii2 framework. Specifically, I will describe how it's possible to connect the AngularJS framework and its application methods. So, let’s get the ball rolling!

As a starting point, here are several notes. I am going to use Yii2 to develop the greater part of our app, while AngularJS will be used strictly for app parts such as modal windows to add offers, search bar, car builder (a specific app’s feature that allows users to build cars they’d like to lease). AngularJS won’t cause any changes to the app’s development route but will help improve its usability and performance.

In the short term, I plan to transform our application into a one-page app. Here’s where AngularJS will be helpful. It will interact with Yii2 REST backend. However, I don’t want to do it at this stage because I will have to handle routing, validation, authorization and other similar operations on AngularJS and the backend alike.

RESTful API Yii provides us with a clean API that can exchange data with a built-in AngularJS application, or with our mobile application in the future.

As performance is a really big deal for our app, I will focus more on REST in the future. A properly structured RESTful app is a boon to the bottom-line of any business, especially if this app has a smart caching system with a flexible strategy behind it. Also, I plan to store out backend and DB on Amazon EC2 server, allowing only JSON data in order to reduce the load on bandwidth. It’s worth mentioning that Amazon EC2 server has a specific feature - automatic scalability - that allows it to automatically adapt computational performance based on site’s traffic. As to the static content in our app, I will store it on the optimized CDN Amazon S3, which is a cost-effective solution with optimal server response rate. (I will write about it in the upcoming posts.)

Backend

Yii2 framework is a set of tools and instruments that facilitates RESTful API implementation. I am going to demonstrate how you can quickly create your own RESTful API stack, not spending much time on the code generation process.

To begin, let’s create an API controller in Lease module, and then place
 modules/lease/controllers/frontend into  ApiController.php. Like this:

namespace modules\lease\controllers\frontend;

use yii\rest\ActiveController;

class ApiController extends ActiveController
{
    public $modelClass = 'modules\lease\models\frontend\Lease';
}

The controller’s class is inherited from  yii\rest\ActiveController , which is responsible for implementation of the set of RESTful operations. Specifying modelClass as  modules\lease\models\frontend\Lease , the controller “gets” which model can be used for data extraction and manipulation.

To enable API manipulation with ajax requests from third-party domains, you need to add  cors  behavior into the controller (if needed):

...
 public function behaviors()
    {
        $behaviors = parent::behaviors();
        $behaviors['corsFilter' ] = [
              'class' => \yii\filters\Cors::className(),
        ];
        // here’s where I add behavior (read below)
        return $behaviors;
    }
...

To note! I need to save behavior’s configuration from  yii\rest\Controller. This is exactly why I have chosen to store it in the first string  $behaviors = parent::behaviors(); . Then, I will keep adding more behaviors.

Yii2 REST API interface can create responses in XML and JSON formats. I need JSON only; so, let’s notify the framework about it:

...
 $behaviors['contentNegotiator'] = [
    'class' => \yii\filters\ContentNegotiator::className(),
    'formats' => [
        'application/json' => \yii\web\Response::FORMAT_JSON,
    ],
];
...

I can configure different access rules for multiple API actions using  yii\filtersAccessControl  class. Thus, let’s specify that only authorized users can create, edit and delete listings in the app. Like this:

...
 $behaviors['access'] = [
    'class' => \yii\filters\AccessControl::className(),
    'only' => ['create', 'update', 'delete'],
    'rules' => [
        [
            'actions' => ['create', 'update', 'delete'],
            'allow' => true,
            'roles' => ['@'],
        ],
    ],
];
...

Also, to control access rules for specific actions, you should override/redefine CheckAccess() method. You need to do it so that you can easily check if this or that user has privileges to do a certain action with this data model. To demonstrate the process, let’s type several lines of code that will help us check if a user can edit or delete the listings:

…
public function checkAccess($action, $model = null, $params = [])
{
    // check if the user can either edit or delete the listing
    // eliminate the exception ForbiddenHttpException if there are no access rights
    if ($action === 'update' || $action === 'delete') {
        if ($model->user_id !== \Yii::$app->user->id)
            throw new \yii\web\ForbiddenHttpException(sprintf('You can only %s lease that you\'ve created.', $action));
    }
}
...

This method will be called automatically for all of the actions related to  yii\rest\ActiveControllerclass. If you create a new action in the controller, which needs to check access rights and privileges, you need to call this method in new actions.

At the second stage, I need to tweak the configuration of urlManager component in module’s bootstrapping class that is stored in  modules/lease/Bootstrap.php . Like this:

...
$app->getUrlManager()->addRules(
   [
       [
           'class' => 'yii\rest\UrlRule',
           'controller' => ['leases' => 'lease/api'],
           'prefix' => 'api'
       ]
   ]
);
…

The configuration demonstrated above adds a specific URL rule for API controller so that data is free to get access to for further manipulation, using short URLs and corresponding HTTP titles. I have also specified prefix in order that all of the URLs that refer to API services can start with  /api/*. Using this pattern you can easily hide your app’s content from search engine crawlers in robots.txt file. From this point on, I will add API controllers to every module of RESTful API interface, using this method.

Having written the code above, I have already solved the task of creating RESTful API interface to access data from Lease model. So far, I have created API interfaces that include the following endpoints:

  • GET /api/leases: to get the listings

  • HEAD /api/leases: to get the response header for GET /api/leases

  • POST /api/leases: to create a new listing

  • GET /api/leases/3: to gather data from a specific listing with id=3

  • HEAD /api/leases/3: to get the response header for GET /api/leases/3

  • PATCH /api/leases/3 and PUT /api/leases/3: to change data of the listing with id=3

  • DELETE /api/leases/3: to delete the listing with id=3

  • OPTIONS /api/leases: to get the list of request options for /api/leases

  • OPTIONS /api/leases/3: to get the list of request options for /api/leases/3

Utilizing Yii2 of RESTful API framework, I have managed to create API endpoints that act like controller operations and are used for a single data type (for one model).

Frontend

After the app’s backend part is done, let's proceed to the frontend part. As a starting point, mount AngularJS. This can be easily done with composer. All I need to do is to add the following code into “require” section of composer.json:

...
"bower-asset/angular": "^1.5",
"bower-asset/angular-animate": "^1.5",
"bower-asset/angular-bootstrap": "^2.2"
…

Then, execute the following command in the command line:  php composer.phar update 

Now, let’s proceed to AssetBundle. I should simply attach AngularJS files to the layout. Here’s how  \frontend\assets\AngularAsset.php file will look like:

namespace frontend\assets;

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

class AngularAsset extends AssetBundle
{
    public $sourcePath = '@bower';
    public $js = [
        'angular/angular.js',
        'angular-animate/angular-animate.min.js',
        'angular-bootstrap/ui-bootstrap.min.js',
        'angular-bootstrap/ui-bootstrap-tpls.min.js'
    ];

    public $jsOptions = [
        'position' => View::POS_HEAD,
    ];
}

 public $jsOptions = [ ‘position’ => View::POS_HEAD, ]; notifies Yii that it needs to attach JavaScript files to <head> of our layout instead of placing these files at the very end of <body>. This way, AngularJS core will perform quickly and nicely. I also need to add this Bundle depending on our main asset bundle application. I should implement  ‘js/app.js’ file as well. I will need it to store the increment of out AngularJS app. Like this:

namespace frontend\assets;

use yii\web\AssetBundle;

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

Conjointly, let’s add this bundle into controllers.js file. From now forth, I will specify controllers for our frontend, as well as directives.js and services.js to describe directives and services in this file.

Now, let’s implement Angular into views of our application by adding  ng-app directive to <html>. By doing this, I will turn on our AngularJS application (let’s just call it “app”). Here’s how:

...
<html lang="<?= Yii::$app->language ?>" data-ng-app="app" >
...

I’d like you to pay attention to the line of code above. My directive is  data-ng-app. If I had done it any other way, the app’s HTML would not have passed W3C validation. As the way I specify directives has no influence on how they operate, but code validity is crucial, I have arrived at the decision to add data- to all non-standard directives.

The code for Angular application will be stored in  frontend/web/js/app.js file. There, I will specify the app and mount the modules I am going to use later on.

'use strict';

var app = angular.module('app', [
    'ngAnimate',
    'ui.bootstrap',
    'controllers'       //our module frontend/web/js/controllers.js
]);

Then, let’s add LeaseService service into  frontend/web/js/services.js file. It will complete CRUD operations for listings by communicating with backend’s REST API. Like this:

'use strict';

var app = angular.module('app');

app.service('LeaseService', function($http) {
    this.get = function() {
        return $http.get('/api/leases');
    };
    this.post = function (data) {
        return $http.post('/api/leases', data);
    };
    this.put = function (id, data) {
        return $http.put('/api/leases/' + id, data);
    };
    this.delete = function (id) {
        return $http.delete('/api/leases/' + id);
    };
});

Currently, our controllers module features only the simplest controller - LeaseController. It asks  /api/leases for the data and passes this data into views. The code below demonstrates how it’s done:

'use strict';

var controllers = angular.module('controllers', []);

controllers.controller('LeaseController', ['$scope', 'LeaseService',
    function ($scope, LeaseService) {
        $scope.leases = [];
        LeaseService.get().then(function (data) {
            if (data.status == 200)
                $scope.leases = data.data;
        }, function (err) {
            console.log(err);
        })
    }
]);

Now, let’s use this controller in  \modules\lease\views\frontend\default\index.php file by declaring it with ng-controller directive. Then, I need to output data, provided by API. Like this:

<div class="lease-default-index" data-ng-controller="LeaseController">
    <div>
        <h1>All Leases</h1>
        <div data-ng-show="leases.length > 0">
            <table class="table table-striped table-hover">
                <thead>
                    <th>Year</th>
                    <th>Make</th>
                    <th>Model</th>
                    <th>Trim</th>
                </thead>
                <tbody>
                <tr data-ng-repeat="lease in leases">
                    <td>{{lease.year}}</td>
                    <td>{{lease.make}}</td>
                    <td>{{lease.model}}</td>
                    <td>{{lease.trim}}</td>
                </tr>
                </tbody>
            </table>
        </div>
        <div data-ng-show="leases.length == 0">
            No results
        </div>
    </div>
</div>

You can access the generated table following this route: /lease/default/index. It features a list of all of the listings that are stored in the DB.

Final Thoughts

Yii2 framework is perfect for creating RESTful API interfaces. It allows you to do everything you need quickly and easily. Use it if you want your apps to perform nicely and the code to be neat and clean.

You can have a closer look at the code showcased in this and previous articles in the GitHub repository.

If you have any questions or suggestions about the article, please leave your comments in the section below, or write directly to Clever-Solution.com.

With SDKs for all major client and server side platforms, you can experiment on any platform with Optimizely Full Stack.

Topics:
application development ,yii framework ,angularjs ,restful api

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}