На данный момент мы имеем готовое 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 поддерживает по этому алгоритму добавление неограниченного количества таблиц, просто нужно дописывать пути в конфигах, добавлять новые контроллеры и закидывать в модель функции для новых таблиц.