A Meteor way to structure AngularJS code wih Laravel 5.1
I have recently started working with MeteorJS, which I find it an interesting tool for creating Web Apps.
In two words about meteor — it has an installer, which includes MongoDB, a webserver and JS interpreter which is some kind of modified NodeJS (though I am not sure), and Meteor backend+frontend framework. All of that is inside a single installer and sets up with two clicks (and later it can be hosted on a subdomain with their origin like {yourappname}.meteor.com with a single comand — like “meteor publish {yourappname}), so I think it is definately worth trying, because it has such a big difference in approach compared to Laravel, ROR, Django and most frameworks, which mostly rely on HTTP requests (Meteor uses a protocol based on websockets which communicates with frontend framework to keep the code updated).
On of the practices of structuring code with Meteor is setting them into folders “client”, “server”, and “common”. The client code is only interpreted by the client side, server code is only interpreted by server, and is not even loaded by the client and common part is available for both sides, which is usefull for validation and other stuff and that folder can have any name, except ‘client’ and ‘server’. When using a router with meteor — your choose what template should be used, and all js code on client side is available to it. So in order to keep understandalbe structure — you most developers put a template file and js file that is only related to that template in the same folder.
I though, why don’t I do the same, with Laravel and Angular? And I did. So here is how it works.
Prerequisites
The article is oriented on devs that already have some Laravel and Angular knowledge. I am sure those, who have a deep understanding of Angular know better approaches for code structuring, though this article might still be interesting for them.
The Idea
Quick description:
- move ‘views’ folder to ‘public’ folder instead of ‘storage’ folder and set this in config.
- bootstrap the angular app in your app.js file
- place any js files related to a single view in the same folder with the same view
Ok, lets discuss the idea. AngularJS and frontend frameworks, like Ember allow you to build single page applications (SPA), and most best practices explain you how to do that. You build an Angular app and use your Laravel/ROR/etc framework as another app only for sending replies to API calls and usually place it in another folder and register a subdomain like api.yourdomain.com .
I have not worked with AngularJS a lot, and what I wanted was to use it to structure my AJAX requests, because using Jquery can easily mess your code when working with AJAX, but I didn’t understand how to properly setup Authentication in Angular+Laravel SPA solution and I also did not want to take routing away from Laravel and pass it to Angular. What I liked in meteor — was that all js files relied to a template were placed in the same folder with that template. It is close to the Angular File Structure suggested in Community-driven set of best practices for AngularJS application development — https://github.com/mgechev/angularjs-style-guide.
Using that with Laravel, we can place:
- page.blade.php
- controller.js
In the same folder which will help us easier understand there relation and search the code. In order to allow such a file structure we need to place the ‘storage/views’ folder inside the ‘public’. And then change its path inside the ‘config/view/paths’ setting so that Laravel would search it correctly.
Setting up the backend
As an exampe application, I will be building a some kind of ToDo app. The purpose of the app, is to set goals and each goal, will have a log, where every accomplishment related to the goal will be recorded and a todo list, where tasks will be added. Actually the log and todo fields won’t be related to each other too much.
If you want to check the backend structure, you can just check the code, which is hosted on Github — https://github.com/naneri/goalkeeper. The backend is built on top of Laravel 5.1. Database consists of 4 tables:
- Users
- Goals
- Logs
- Todos
Users have Goals, Goals have Logs and Todos.
Frontend and structure
First we need an app.js file — that will bootstrap the app and in which we will declare the global dependancies that are necessary for all pages.
<script src="{{asset('js/angular.js')}}"></script> <script src="{{asset('views/js/app.js')}}"></script>
We create an additional ‘js/app.js’ in the views folder but what I realized after finishing the app thats not necessary. In app.js I need to setup a config which will allow me to get the base_url of the app, because I can’t blindly send them to relative path routes, due to the fact that routes are generated by Laravel, not Angular.
// views/js/app.js
angular.module('goalApp', [])
.value('base_url', 'http://goal.local');
That way, we can use ‘base_ur’, to retrieve the base URL.
The goal creating code has nothing special compared to any other Laravel model+view+controller and route, so I won’t show it here — you can check it on github.
Here is the template of a goal/show. The code has nothing special — so you don’t have to read it if you don’t want to. The only thing to note — is the way angular controller are included in the end.
// goal/show/main.blade.php
@extends(‘misc._layout’)
@section(‘content’) <div class=”col-md-8"> <h1>{{$goal->title}}</h1> <div ng-controller=”LogsController as logs” ng-init=”logs.goalId = {{$goal->id}}”> <form ng-submit=”logs.saveNewLog()”> <input type=”hidden” ng-init=”logs.init({{$goal->id}})”> <input type=”text” ng-model=”logs.newLog.content”> <button type=”submit” >Save</button> </form> <div ng-repeat=”log in logs.logList”> <p>@{{log.created_at}} | @{{log.content}}</p> </div> </div> </div> <div class=”col-md-4"> <h1>Todos</h1> <div ng-controller=”TodosController as todos” ng-init=”todos.goalId = {{$goal->id}}”> <form ng-submit=”todos.saveNewTodo()”> <input type=”hidden” ng-init=”todos.init({{$goal->id}})”> <input type=”text” ng-model=”todos.newTodo.title”> <button type=”submit”>Save</button> </form> <div ng-repeat=”todo in todos.todoList”> <p ng-show=”!todo.done”> <input type=”checkbox” ng-model=”todo.done” ng-checked=”todo.done” ng-click=”todos.todoStatus(todo)”> <span class=”@{{todo.done && ‘strike’}}”>@{{todo.title}}</span> </p> </div> <hr> <div ng-repeat=”todo in todos.todoList”> <p ng-show=”todo.done”> <input type=”checkbox” ng-model=”todo.done” ng-checked=”todo.done” ng-click=”todos.todoStatus(todo)”> <span class=”@{{todo.done && ‘strike’}}”>@{{todo.title}}</span> </p> </div> </div> </div> <script src=”{{asset(‘views/goal/show/logsController.js’)}}”></script> <script src=”{{asset(‘views/goal/show/todosController.js’)}}”></script> @stop
I will only provide logsController code, because todos, is almost the same, and there is not much needed to explain.
// goal/show/logsController.js
angular.module(‘goalApp’).controller(‘LogsController’, LogsController);
LogsController.$inject = [ ‘$scope’, ‘$http’, ‘base_url’];
Note that ‘base_url’ value is injected for later usage.
// goal/show/logsController.js
function LogsController ($scope, $http, base_url){
var logs = this;
logs.newLog = {};
logs.logList = {};
logs.init = function(goalId){
var url = base_url + ‘/api/goal/’ + goalId + ‘/logs/’;
console.log(url)
$http.get(base_url + ‘/api/goal/’ + goalId + ‘/logs/’)
.success(function(data){
console.log(data);
logs.logList = data;
});
}
logs.saveNewLog = function(){
logs.newLog.goal_id = logs.goalId;
$http.post(base_url + ‘/logs/store’, logs.newLog)
.success(function(data){
logs.logList.unshift(data);
logs.newLog = {};
})
}
}
As you can see, nothing special in the code.
Conclusion
So, one more time — here are the steps to structure Laravel+Angular app in the meteor way:
- move ‘views’ folder to ‘public’ folder instead of ‘storage’ folder and set this in config.
- bootstrap the angular app in your app.js file
- place any js files related to a single view in the same folder with the same view
The code is at https://github.com/naneri/goalkeeper.
Comments
Post a Comment