На данный момент мы имеем готовое RESTful api приложение в серверной части и простое AngularJS приложение в клиентской части. Дело за малым, обеспечить связь второго с данными из первого.
Доработаем главный модуль app.js
Добавим описание модуля, обеспечивающего работу с данными о фильмах, yii2AngApp.film:
...
var yii2AngApp = angular.module('yii2AngApp', [
  'ngRoute',
  'yii2AngApp.site',
  'yii2AngApp.film'
]);
// рабочий модуль
var yii2AngApp_site = angular.module('yii2AngApp.site', ['ngRoute']);
var yii2AngApp_film = angular.module('yii2AngApp.film', ['ngRoute']);
...
Модель данных для фильмов
Создадим новый файл client/models/film.js, он будет отвечать за crud операции через rest. Функционально, он будет аналогом модели в yii2.
'use strict';
yii2AngApp_film.factory("services", ['$http','$location','$route', 
    function($http,$location,$route) {
    var obj = {};
    obj.getFilms = function(){
        return $http.get(serviceBase + 'films');
    }    
    obj.createFilm = function (film) {
        return $http.post( serviceBase + 'films', film )
            .then( successHandler )
            .catch( errorHandler );
        function successHandler( result ) {
            $location.path('/film/index');            
        }
        function errorHandler( result ){
            alert("Error data")
            $location.path('/film/create')
        }
    };    
    obj.getFilm = function(filmID){
        return $http.get(serviceBase + 'films/' + filmID);
    }
    obj.updateFilm = function (film) {
        return $http.put(serviceBase + 'films/' + film.id, film )
            .then( successHandler )
            .catch( errorHandler );
        function successHandler( result ) {
            $location.path('/film/index');
        }
        function errorHandler( result ){
            alert("Error data")
            $location.path('/film/update/' + film.id)
        }    
    };    
    obj.deleteFilm = function (filmID) {
        return $http.delete(serviceBase + 'films/' + filmID)
            .then( successHandler )
            .catch( errorHandler );
        function successHandler( result ) {
            $route.reload();
        }
        function errorHandler( result ){
            alert("Error data")
            $route.reload();
        }    
    };    
    return obj;   
}]);
Функции obj.getFilm, obj.createFilm, obj.updateFilm и другие, являются транспортом для соответствующих crud операций через rest. Для примера, код:
obj.createFilm = function (film) {
        return $http.post( serviceBase + 'films', film )
        ...
    };
Создает новый фильм путем запроса методом POST.
obj.getFilm = function(filmID){
        return $http.get(serviceBase + 'films/' + filmID);
    }
Получает данные фильма с заданным id методом запроса GET.
obj.updateFilm = function (film) {
        return $http.put(serviceBase + 'films/' + film.id, film )
         ...
    };
Вносит изменения в данные фильма методом запроса UPDATE.
obj.deleteFilm = function (filmID) {
        return $http.delete(serviceBase + 'films/' + filmID)
        ...    
    };
Удаляет запись с соответствующим id методом запроса DELETE.
Контроллер модуля yii2AngApp_film
Создадим новый файл client.local/controllers/film.js.
'use strict';
yii2AngApp_film.config(['$routeProvider', function($routeProvider) {
  $routeProvider
    .when('/film/index', {
        templateUrl: 'views/film/index.html',
        controller: 'index'
    })
    .when('/film/create', {
        templateUrl: 'views/film/create.html',
        controller: 'create',
        resolve: {
            film: function(services, $route){
                return services.getFilms();
            }
        }
    })
    .when('/film/update/:filmId', {
        templateUrl: 'views/film/update.html',
        controller: 'update',
        resolve: {
          film: function(services, $route){
            var filmId = $route.current.params.filmId;
            return services.getFilm(filmId);
          }
        }
    })
    .when('/film/delete/:filmId', {
        templateUrl: 'views/film/index.html',
        controller: 'delete',
    })
    .otherwise({
        redirectTo: '/film/index'
    });
}]);
yii2AngApp_film.controller('index', ['$scope', '$http', 'services', 
    function($scope,$http,services) {
    $scope.message = 'Everyone come and see how good I look!';
    services.getFilms().then(function(data){
        $scope.films = data.data;
    });    
    $scope.deleteFilm = function(filmID) {
        if(confirm("Are you sure to delete film number: " + filmID)==true && filmID>0){
            services.deleteFilm(filmID);    
            $route.reload();
        }
    };
}])
.controller('create', ['$scope', '$http', 'services','$location','film', 
    function($scope,$http,services,$location,film) {
    $scope.message = 'Look! I am an about page.';
    $scope.createFilm = function(film) {
        var results = services.createFilm(film);
    }  
}])
.controller('update', ['$scope', '$http', '$routeParams', 'services','$location','film', 
    function($scope,$http,$routeParams,services,$location,film) {
    $scope.message = 'Contact us! JK. This is just a demo.';
    var original = film.data;
    $scope.film = angular.copy(original);
    $scope.isClean = function() {
        return angular.equals(original, $scope.film);
    }
    $scope.updateFilm = function(film) {    
        var results = services.updateFilm(film);
    } 
}]);
Структура аналогична контроллеру site. Сначала описываем маршруты и обслуживающие их функции и представления.
Шаблоны для модуля yii2AngApp_film
Файлы шаблонов будем хранить в каталоге client/views/film. Шаблоны будут содержать html разметку и элементы данных нашего angularjs приложения.
client/views/film/index.html
<div>
    <h1>Каталог фильмов</h1>    
    <p>{{ message}}</p>
    <div ng-show="films.length > 0">
        <a class="btn btn-primary" href="#/film/create">
            <i class="glyphicon glyphicon-plus"></i> Добавить
        </a>
        <table class="table table-striped table-hover">
            <thead>
            <th>Название</th>
            <th>Режиссер</th>
            <th>Описание</th>
            <th>Год</th>
            <th style="width:80px;">Действия </th>
            </thead>
            <tbody>
                <tr ng-repeat="data in films">
                    <td>{{data.title}}</td>
                    <td>{{data.director}}</td>
                    <td>{{data.storyline}}</td>
                    <td>{{data.year}}</td>
                    <td>
                        <a class="btn btn-primary btn-xs" href="#/film/update/{{data.id}}">
                            <i class="glyphicon glyphicon-pencil"></i>
                        </a> 
                        <a class="btn btn-danger btn-xs" ng-click="deleteFilm(data.id)">
                            <i class="glyphicon glyphicon-trash"></i>
                        </a>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>
    <div ng-show="films.length == 0">
        Каталог фильмов пуст
    </div>
</div>
client/views/film/create.html
<div>
    <h1>Каталог фильмов: добавление фильма</h1>
    <p>{{ message}}</p>
    <form role="form" name="myForm">
        <div class= "form-group" ng-class="{error: myForm.title.$invalid}">
            <label> Название </label>
            <div>
                <input name="title" ng-model="film.title" type= "text" class= "form-control" placeholder="Название фильма" required/>
                <span ng-show="myForm.title.$dirty && myForm.title.$invalid" class="help-inline">Название обязательно</span>
            </div>
        </div>
        <div class= "form-group">
            <label> Описание </label>
            <div>
                <textarea name="storyline" ng-model="film.storyline" class= "form-control" placeholder= "Описание фильма"></textarea>
            </div>
        </div>
        <div class= "form-group" ng-class="{error: myForm.director.$invalid}">
            <label> Режиссер </label>
            <div>
                <input name="director" ng-model="film.director" type= "text" class= "form-control" placeholder="Режиссер" required/>
                <!--<span ng-show="myForm.director.$dirty && myForm.director.$invalid" class="help-inline">Режиссер обязателен</span>-->
            </div>
        </div>
        <div class= "form-group" ng-class="{error: myForm.year.$invalid}">
            <label> Год </label>
            <div>
                <input name="year" ng-model="film.year" type= "text" class= "form-control" placeholder="Год год выпуска" required/>
                <span ng-show="myForm.year.$dirty && myForm.year.$invalid" class="help-inline">Год обязателен</span>
            </div>
        </div>
        <a href="#/film/index" class="btn btn-default">Cancel</a>
        <button ng-click="createFilm(film);" 
                ng-disabled="myForm.$invalid"
                type="submit" class="btn btn-default">Submit</button>
    </form>
</div>
client/views/film/update.html
<div>
    <h1>Каталог фильмов: редактирование фильма</h1>
    <p>{{ message}}</p>
    <form role="form" name="myForm">
        <div class= "form-group" ng-class="{error: myForm.title.$invalid}">
            <label> Название </label>
            <div>
                <input name="title" ng-model="film.title" type= "text" class= "form-control" placeholder="Название фильма" required/>
                <span ng-show="myForm.title.$dirty && myForm.title.$invalid" class="help-inline">Название обязательно</span>
            </div>
        </div>
        <div class= "form-group">
            <label> Описание </label>
            <div>
                <textarea name="storyline" ng-model="film.storyline" class= "form-control" placeholder= "Описание фильма"></textarea>
            </div>
        </div>
        <div class= "form-group" ng-class="{error: myForm.director.$invalid}">
            <label> Режиссер </label>
            <div>
                <input name="director" ng-model="film.director" type= "text" class= "form-control" placeholder="Режиссер" required/>
                <!--<span ng-show="myForm.director.$dirty && myForm.director.$invalid" class="help-inline">Режиссер обязателен</span>-->
            </div>
        </div>
        <div class= "form-group" ng-class="{error: myForm.year.$invalid}">
            <label> Year </label>
            <div>
                <input name="year" ng-model="film.year" type= "text" class= "form-control" placeholder="Год выпуска" required/>
                <span ng-show="myForm.year.$dirty && myForm.year.$invalid" class="help-inline">Year Required</span>
            </div>
        </div>
        <a href="#/film/index" class="btn btn-default">Cancel</a> 
        <button ng-click="updateFilm(film);" 
                ng-disabled="isClean() || myForm.$invalid"
                type="submit" class="btn btn-default">Submit</button>
    </form>
</div>
Последние штрихи
Не забываем добавить ссылку на каталог в меню:
...
<ul class="nav navbar-nav navbar-right">
    <li><a href="#/"><i class="glyphicon glyphicon-home"></i> Главная</a></li>
    <li><a href="#/film/index"><i class="glyphicon glyphicon-film"></i> Каталог фильмов</a></li>
...
и подключить модель и контроллер каталога:
...
    <script src="models/film.js"></script>
    <script src="controllers/film.js"></script>
...
Готово
Проверяем работу нашего каталога фильмов:
Заключение
Задача минимум выполнена: мы можем легко добавлять, редактировать и удалять нужные элементы каталога. Конечно, это примитивное приложение, однако, оно было создано с целью показать, как легко и быстро, буквально за 20-30 минут, можно собрать современное html5 приложение с использованием Yii 2.0, AngularJS и немножко Twitter Bootstrap.
При подготовке использованы материалы:

такую ошибку выдает
GET http://server.clientfilms/ net::ERR_NAME_NOT_RESOLVED
В url ошибка, возможно должно быть http://server.local/films/ ?
оо спс заработало
вообще неправильно маршруты в js-файлах хардкодить. а поменяется если что-то в url-менеджере. добавите/уберете суффикс, да мало ли что. и что все в js вручную переписывать? о_О
Добрый день! Почему может возникать такая штука?
OPTIONS http://rest.server.ru/films 403 (Forbidden) angular.js:10514
XMLHttpRequest cannot load http://rest.server.ru/films. No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://rest.client.ru’ is therefore not allowed access.
Похоже на проблему CORS. Если браузер хром — попробуйте другой.
Проверьте, передается ли заголовок Access-Control-Allow-Origin в запросе. Пример.
Почему после добавления контроллера {{ message}} на главной странице (контроллер site) стал выводить message из index контроллера film $scope.message = ‘Everyone come and see how good I look!’;
В чем может быть проблема?
Здравствуйте, всё заработало с первого раза.
Но есть некоторые моменты.
При удалении фильма ругается на то, что не определён роут.
Консоль:
ReferenceError: $route is not defined
at r.$scope.deleteFilm (http://site.lock/controllers/film.js:48:17)
at fn (eval at (http://site.lock/assets/angular/angular.min.js:216:110), :2:382)
at Jc.(anonymous function).compile.d.on.e (http://site.lock/assets/angular/angular.min.js:257:177)
at r.$get.r.$eval (http://site.lock/assets/angular/angular.min.js:133:446)
at r.$get.r.$apply (http://site.lock/assets/angular/angular.min.js:134:175)
at HTMLAnchorElement. (http://site.lock/assets/angular/angular.min.js:257:229)
at Of (http://site.lock/assets/angular/angular.min.js:35:394)
at HTMLAnchorElement.d (http://site.lock/assets/angular/angular.min.js:35:341)
Подскажите пожалуйста как его определить
Ошибка возникает из-за того, что в film контроллере стоит тоже $route.reload(); в 46 строке, а он не прописан в объявлении контроллера. Надо его добавить вот так:
yii2AngApp_film.controller(‘index’, [‘$scope’, ‘$http’, ‘services’, ‘$route’,
function($scope,$http,services, $route) { — тогда ошибки не будет, но и перезагрузка не произойдет. Это уже другая ошибка
Добрый вечер! Подскажите, почему не выводится список фильмов http://u.to/s0XeDg
Спасибо!
по ссылке выводится список фильмов;)
Ага) Разобрался. Не поменял урл в app.js. Спасибо.
А как быть с загрузкой файлов?
Для этого есть библиотеки, нужно просто загуглить.
Подскажите как сделать авторизацию при таком подходе (полное разделение клиентской части на ангуляре и серверное на пхп)?
поддерживаю вопрос
Общая схема такая:
1. Клиентское приложение (angular) запрашивает у пользователя логин/пароль;
2. Клиентское приложение, на основании логина и пароля, получает от сервера (yii2) авторизационный токен и сохраняет его;
3. В каждый запрос к серверу, клиентское приложение включает авторизационный токен;
4. На основании полученного авторизационного токена, сервер определяет пользователя и работает дальше/отказывает, если недостаточно прав или не верный токен.
Для конкретной реализации можно взять https://jwt.io (есть библиотеки под все современные языки).
В приложении Yii2 нужно для класса, реализующего \yii\web\IdentityInterface, реализовать метод findIdentityByAccessToken().
Круто! То что нужно для изучения REST с помощью Angular и Yii2. Автору респект! Отличный материал! Правда кое где по коду замечал косячки, исправлял у себя и всё получилось! Спасибо!
Классный материал! Но вот есть один момент. Когда создаю вторую страницу и размещаю на ней данные из другой таблицы, но той же базы, то каталог с фильмами перестает грузиться. В консоли сразу видно, что всегда прогружаются данные для второй таблицы. Как это можно исправить?
Анна, если вы это исправили, скажите как?
Получилось добавить еще одну таблицу. на другую вкладку.Проблема в том, что при добавлении в зависимость модулей, каждый последующий перезаписывает предыдущий. Исправлял так: в app.js убрал модуль yii2AngApp.film, из контроллера film.js перенес данные в site.js(Т.е прописать в site.js пути и функции для своего view). После создал модель site.js и данные из модели film.js перенес туда. Все, теперь site.js поддерживает по этому алгоритму добавление неограниченного количества таблиц, просто нужно дописывать пути в конфигах, добавлять новые контроллеры и закидывать в модель функции для новых таблиц.