PHP
Article

Flexible and Easily Maintainable Laravel + Angular Material Apps

By Jad Joubran

In this article, we’re going to set up a Laravel API with Angular Material for the front end. We’re also going to follow best practices that will help us scale with the number of developers working on the project and the complexity behind it. Most tutorials cover this topic from another perspective – they completely forget about scaling. While this tutorial is not targeted at small todo apps, it is extremely helpful if you’re planning to work with other developers on a big project.

Laravel angular material

Here’s a demo built with Laravel and Angular Material.

Setting up Laravel

Creating a project

We’re going to start by pulling in the latest Laravel – 5.1 at the time of writing.

composer create-project laravel/laravel myapp --prefer-dist

Configuring the environment

All the subsequent commands will be ran inside the myapp directory. Throughout the remainder of this tutorial, we’ll assume you’re running a Linux environment if you’re following along. If you aren’t you’re encouraged to install Homestead Improved as a virtual Linux environment in which to follow along.

cd myapp

Next, we’re going to update our .env file with the database connection credentials:

DB_HOST=localhost
DB_DATABASE=your-db-name
DB_USERNAME=your-username
DB_PASSWORD=your-password

Once your app has been configured, you should see the Laravel 5 greeting page.

Laravel 5

Debugbar

Laravel debug bar is one of the most useful Laravel packages.

Debugbar makes it easy to debug any of the following:

  • exceptions
  • views
  • messages
  • queries
  • mails
  • auth
  • routes
  • ajax
  • and more

Laravel debugbar

So let’s go ahead and install it using composer

composer require barryvdh/laravel-debugbar

Next, we need to open config/app.php and:

  • add Barryvdh\Debugbar\ServiceProvider::class, to the list of providers
  • add 'Debugbar' => Barryvdh\Debugbar\Facade::class, to the list of aliases

Refresh the page and you should see debugbar at the bottom of the page!

Debugbar runs automatically if you have the APP_DEBUG flag enabled in your .env file.

Build automation

Laravel’s Elixir is a layer on top of gulp that makes it easier to write gulp tasks.

Setup

We’ll start by installing gulp globally. Note that you need nodeJS installed for this section.

npm install -g gulp

and then we need to grab a few packages that will make our lives easier, so replace your package.json with the following:

{
  "dependencies": {
    "gulp-concat": "^2.6.0",
    "gulp-concat-sourcemap": "^1.3.1",
    "gulp-filter": "^1.0.2",
    "gulp-if": "^1.2.5",
    "gulp-jshint": "^1.9.0",
    "gulp-minify-css": "^0.3.11",
    "gulp-ng-annotate": "^1",
    "gulp-notify": "^2.0.0",
    "gulp-sourcemaps": "^1",
    "gulp-uglify": "^1",
    "jshint-stylish": "^2",
    "laravel-elixir": "^3.0.0",
    "laravel-elixir-livereload": "^1.1.1",
    "main-bower-files": "^2.1.0"
  },
  "devDependencies": {
    "gulp": "^3.9.0"
  }
}

Then install these packages.

npm install

Managing front-end dependencies

I like to use Bower because of its flat dependency tree, but that’s really up to your preference.
You can use npm, requirejs or just the plain old browse-to-url-and-download-and-then-manually-check-for-updates.

Let’s install bower globally.

npm install -g bower

Then add this line to .gitignore:

/bower_components

and run bower init to create a new bower.json file which will be committed in the repository.

Folder by Feature

Then, we want to choose a location for Angular within our Laravel folder.

Most people prefer to add it to resources/js but since I prefer to have a folder by feature architecture, we’re going to create an angular folder at the root level.

I chose this setup because we want it to be able to scale with the number of developers and with the business complexity behind it.

A few months from now, we’re going to have a folder for every feature. Here’s an example of the folders that we are going to have inside angular/app:

  • header
    • header.html
    • header.js
    • header.less
  • login
    • login.html
    • login.js
    • login.less
    • service.js
  • landing
  • users
  • change_password
  • reset_password
  • list_items
  • add_item
  • item_details

It’s much easier to fix bugs in the list_items page. We just have to open the list_items folder where we find all the related files for this feature:

  • templates
  • Less files
  • Angular controller(s)
  • Angular service(s).

Compare this to having to open a folder for each of these files when debugging a single feature.

So let’s go ahead and create the following folders:

  • angular
  • angular/app
  • angular/services
  • angular/filters
  • angular/directives
  • angular/config

Elixir configuration

Now we need to configure elixir to compile a js/vendor.js and a css/vendor.css file from the bower_components folder.

Then we need to run our angular folder through the elixir angular task, which does the following:

  • concatenates our app files into public/js/app.js
  • validates with jshint (if the .jshintrc file is available). If the file is available, it won’t recompile our source code if your code does not pass validation.
  • automatic dependency annotation using ng-annotate

We need to compile our less files. If you’re using Sass, you probably know how to make it work for Sass.

We also need to copy our views from within the angular directory to the public directory.

Finally, we are going to set up livereload. Soft reloads are used for CSS, meaning that a newer version of your css will be injected without reloading the page.

Let’s open gulpfile.js and replace the sample Elixir function call with the following:

var elixir = require('laravel-elixir');
require('./tasks/angular.task.js');
require('./tasks/bower.task.js');
require('laravel-elixir-livereload');

elixir(function(mix){
    mix
        .bower()
        .angular('./angular/')
        .less('./angular/**/*.less', 'public/css')
        .copy('./angular/app/**/*.html', 'public/views/app/')
        .copy('./angular/directives/**/*.html', 'public/views/directives/')
        .copy('./angular/dialogs/**/*.html', 'public/views/dialogs/')
        .livereload([
            'public/js/vendor.js',
            'public/js/app.js',
            'public/css/vendor.css',
            'public/css/app.css',
            'public/views/**/*.html'
        ], {liveCSS: true});
});

If you noticed, I’m loading a couple of custom tasks, so you just need to create the tasks folder at the root directory of the project and create these 2 files:

tasks/angular.task.js

/*Elixir Task
*copyrights to https://github.com/HRcc/laravel-elixir-angular
*/
var gulp = require('gulp');
var concat = require('gulp-concat');
var sourcemaps = require('gulp-sourcemaps');
var jshint = require('gulp-jshint');
var stylish = require('jshint-stylish');
var uglify = require('gulp-uglify');
var ngAnnotate = require('gulp-ng-annotate');
var notify = require('gulp-notify');
var gulpif = require('gulp-if');

var Elixir = require('laravel-elixir');

var Task = Elixir.Task;

Elixir.extend('angular', function(src, output, outputFilename) {

    var baseDir = src || Elixir.config.assetsPath + '/angular/';

    new Task('angular in ' + baseDir, function() {
        // Main file has to be included first.
        return gulp.src([baseDir + "main.js", baseDir + "**/*.js"])
            .pipe(jshint())
            .pipe(jshint.reporter(stylish))
            //.pipe(jshint.reporter('fail')).on('error', onError) //enable this if you want to force jshint to validate
            .pipe(gulpif(! config.production, sourcemaps.init()))
            .pipe(concat(outputFilename || 'app.js'))
            .pipe(ngAnnotate())
            .pipe(gulpif(config.production, uglify()))
            .pipe(gulpif(! config.production, sourcemaps.write()))
            .pipe(gulp.dest(output || config.js.outputFolder))
            .pipe(notify({
                title: 'Laravel Elixir',
                subtitle: 'Angular Compiled!',
                icon: __dirname + '/../node_modules/laravel-elixir/icons/laravel.png',
                message: ' '
            }));
    }).watch(baseDir + '/**/*.js');

});

tasks/bower.task.js

/*Elixir Task for bower
* Upgraded from https://github.com/ansata-biz/laravel-elixir-bower
*/
var gulp = require('gulp');
var mainBowerFiles = require('main-bower-files');
var filter = require('gulp-filter');
var notify = require('gulp-notify');
var minify = require('gulp-minify-css');
var uglify = require('gulp-uglify');
var concat_sm = require('gulp-concat-sourcemap');
var concat = require('gulp-concat');
var gulpIf = require('gulp-if');

var Elixir = require('laravel-elixir');

var Task = Elixir.Task;

Elixir.extend('bower', function(jsOutputFile, jsOutputFolder, cssOutputFile, cssOutputFolder) {

    var cssFile = cssOutputFile || 'vendor.css';
    var jsFile = jsOutputFile || 'vendor.js';

    if (!Elixir.config.production){
        concat = concat_sm;
    }

    var onError = function (err) {
        notify.onError({
            title: "Laravel Elixir",
            subtitle: "Bower Files Compilation Failed!",
            message: "Error: <%= error.message %>",
            icon: __dirname + '/../node_modules/laravel-elixir/icons/fail.png'
        })(err);
        this.emit('end');
    };

    new Task('bower-js', function() {
        return gulp.src(mainBowerFiles())
            .on('error', onError)
            .pipe(filter('**/*.js'))
            .pipe(concat(jsFile, {sourcesContent: true}))
            .pipe(gulpIf(Elixir.config.production, uglify()))
            .pipe(gulp.dest(jsOutputFolder || Elixir.config.js.outputFolder))
            .pipe(notify({
                title: 'Laravel Elixir',
                subtitle: 'Javascript Bower Files Imported!',
                icon: __dirname + '/../node_modules/laravel-elixir/icons/laravel.png',
                message: ' '
            }));
    }).watch('bower.json');


    new Task('bower-css', function(){
        return gulp.src(mainBowerFiles())
            .on('error', onError)
            .pipe(filter('**/*.css'))
            .pipe(concat(cssFile))
            .pipe(gulpIf(config.production, minify()))
            .pipe(gulp.dest(cssOutputFolder || config.css.outputFolder))
            .pipe(notify({
                title: 'Laravel Elixir',
                subtitle: 'CSS Bower Files Imported!',
                icon: __dirname + '/../node_modules/laravel-elixir/icons/laravel.png',
                message: ' '
            }));
    }).watch('bower.json');

});

These 2 gulp tasks might look a bit complex, but you don’t have to worry about that. You’re here to speed up your development process rather than wasting time setting up build tools.

The reason why I’m not using some of the available node modules that are available online, is because, at the time of writing, they are slow and often limited in terms of functionality. Enjoy!

Setting up AngularJS

Installation

Let’s get started by downloading the latest version of Angular 1

bower install angular#1 --save

Don’t forget the --save flag because we want this to be saved in bower.json

We can now run gulp && gulp watch.

Configuring Main Module

Let’s start by configuring angular/main.js

(function(){
"use strict";

var app = angular.module('app',
        [
        'app.controllers',
        'app.filters',
        'app.services',
        'app.directives',
        'app.routes',
        'app.config'
        ]);

    angular.module('app.routes', []);
    angular.module('app.controllers', []);
    angular.module('app.filters', []);
    angular.module('app.services', []);
    angular.module('app.directives', []);
    angular.module('app.config', []);
    })();

Bridging Laravel & Angular

Serving the App

We need to create a new controller using artisan.

php artisan make:controller AngularController --plain
AngularController.php


public function serveApp(){
    return view('index');
}

and then replace the existing Route::get('/', ...) in routes.php with the following:

Route::get('/', 'AngularController@serveApp');

This seems useless at first, but I like to keep the logic outside the Routes file, so I prefer not to have closures there. Eventually, we’re going to use this controller for other methods, like the unsupported browser page.

Then, we need to create the view resources/views/index.blade.php and add this HTML:

<html ng-app="app">
<head>
    <link rel="stylesheet" href="/css/vendor.css">
    <link rel="stylesheet" href="/css/app.css">
</head>
<body>

    <md-button class="md-raised md-primary">Welcome to Angular Material</md-button>

    <script src="/js/vendor.js"></script>
    <script src="/js/app.js"></script>
</body>
</html>

Unsupported Browser

Angular Material is targeted at evergreen browsers, so we need to add a page for unsupported ones ( IE <= 10 ).

We start by adding the following conditional comment in the <head> of our index.blade.php view:

<!--[if lte IE 10]>
    <script type="text/javascript">document.location.href ='/unsupported-browser'</script>
    <![endif]-->

This will redirect the user with an unsupported browser to /unsupported-browser, a route which we should handle in routes.php:

Route::get('/unsupported-browser', 'AngularController@unsupported');

Then, inside AngularController we create the unsupported method:

public function unsupported(){
    return view('unsupported');
}

Finally, we create the unsupported.blade.php view and output a message telling the user that they needs to upgrade to a modern browser.

Pulling in Angular Material

Angular Material is an implementation of Material Design in Angular.js. It provides a set of reusable, well-tested, and accessible UI components based on the Material Design system.

Installation

First we pull Angular Material using Bower:

bower install angular-material --save

Then, we add the ngMaterial module to the app.controllers and app.config modules:

angular.module('app.controllers', ['ngMaterial']);
angular.module('app.config', ['ngMaterial']);

Finally, we re-run gulp watch.

Custom theming

You probably love the feel that Angular Material gives to your app, but you’re worried that it’ll look exactly like Angular Material.

We’ll want to apply some branding guidelines to the theme, which is why we need to create a new config provider for Angular Material which allows us to specify the 3 main colors for our theme:

/angular/config/theme.js:


(function(){
    "use strict";

    angular.module('app.config').config( function($mdThemingProvider) {
        /* For more info, visit https://material.angularjs.org/#/Theming/01_introduction */
        $mdThemingProvider.theme('default')
        .primaryPalette('teal')
        .accentPalette('cyan')
        .warnPalette('red');
    });

})();

Setting up ui-router

ui-router is the de-facto solution to flexible routing with nested views.

Installation

Let’s start by pulling that in using bower:

bower install  ui-router --save

Then, add the ui.router module to the app.routes and app.controllers modules:

angular.module('app.routes', ['ui.router']);
angular.module('app.controllers', ['ngMaterial', 'ui.router']);

Then, we re-run gulp watch.

Configuring routes

Now we need to set up our routes file. Let’s go ahead and create angular/routes.js

(function(){
    "use strict";

    angular.module('app.routes').config( function($stateProvider, $urlRouterProvider ) {

        var getView = function( viewName ){
            return '/views/app/' + viewName + '/' + viewName + '.html';
        };

        $urlRouterProvider.otherwise('/');

        $stateProvider
        .state('landing', {
            url: '/',
            views: {
                main: {
                    templateUrl: getView('landing')
                }
            }
        }).state('login', {
            url: '/login',
            views: {
                main: {
                    templateUrl: getView('login')
                },
                footer: {
                    templateUrl: getView('footer')
                }
            }
        });

    } );
})();

We created 2 routes, one for the Landing page and the other for the Login page. Notice how we have multiple named views. We need to add that to our main view, inside the <body>:

<div ui-view="main"></div>
<div ui-view="footer"></div>

We then create the following folders and files inside angular/app:

  • landing/landing.html with the output Landing view
  • login/login.html with the output Login view
  • footer/footer.html with the output Footer view

Now, whenever you need to add a new page, you just have to add a new .state().

Setting up Restangular

Restangular is an AngularJS service that simplifies common ajax calls to a RESTful API.

Installation

Restangular is a perfect fit with our Laravel API.

Let’s grab the latest version of Restangular by running the following:

bower install restangular --save

Then, add the restangular module to the app.controllers module:

angular/main.js:

angular.module('app.controllers', ['ngMaterial', 'ui.router', 'restangular']);

Then, re-run gulp watch.

Let’s set up a sample API endpoint.

php artisan make:controller DataController --plain
DataController.php:

public function index(){
    return ['data', 'here'];
}
app\Http\routes.php:

Route::get('/data', 'DataController@index');

Sample Usage:

Restangular.all('data').doGET().then(function(response){
    window.console.log(response);
});

Toast

A toast provides simple feedback about an operation in a small popup.

Toast

Since we’re going to use toasts a lot in our application, we’re going to create a toast service angular/services/toast.js

(function(){
    "use strict";

    angular.module("app.services").factory('ToastService', function( $mdToast ){

        var delay = 6000,
        position = 'top right',
        action = 'OK';

        return {
            show: function(content) {
                return $mdToast.show(
                    $mdToast.simple()
                    .content(content)
                    .position(position)
                    .action(action)
                    .hideDelay(delay)
                    );
            }
        };
    });
})();

And now here’s how we can use it in our app:

(function(){
    "use strict";

    angular.module('app.controllers').controller('TestCtrl', function( ToastService ){
                    ToastService.show('User added successfully');
            };

        });

})();

Dialogs

Dialogs are one of the most useful features available in Angular Material. They’re very similar to Modals in Twitter Bootstrap.

Material Dialog

Dialogs are a key component in Single Page Apps, that’s why we’re going to write a powerful dialog service /angular/services/dialog.js

(function(){
    "use strict";

    angular.module("app.services").factory('DialogService', function( $mdDialog ){

        return {
            fromTemplate: function( template, $scope ) {

                var options = {
                    templateUrl: '/views/dialogs/' + template + '/' + template + '.html'
                };

                if ( $scope ){
                    options.scope = $scope.$new();
                }

                return $mdDialog.show(options);
            },

            hide: function(){
                return $mdDialog.hide();
            },

            alert: function(title, content){
                $mdDialog.show(
                    $mdDialog.alert()
                    .title(title)
                    .content(content)
                    .ok('Ok')
                    );
            }
        };
    });
})();

We created 3 methods inside this service:

  • alert(title, content) allows us to display a dialog with a title and a message. Useful for error and success messages
  • hide() hides the dialog
  • fromTemplate(template, $scope) creates a dialog from a template stored in /angular/dialogs/. Useful for Login, Reigster, etc. dialogs. You can create your own component inside the /angular/dialogs/ directory using the same folder by feature approach. You can also pass $scope to the dialog, which will give you access to the $parent scope from within the dialog’s controller.

We just need to fix the Elixir configuration to watch and copy the /angular/dialogs folder:

 .copy('angular/dialogs/**/*.html', 'public/views/dialogs/');

And now here’s how we can use it in our app:

(function (){
    "use strict";

    angular.module('app.controllers').controller('DialogTestCtrl', function ($scope, DialogService){


            $scope.addUser = function(){
                return DialogService.fromTemplate('add_user', $scope);
            };

            $scope.success = function(){
                return DialogService.alert('Success', 'User created successfully!');
            };


        });

})();

Deployment

Here’s a plain bash script that we’re going to use for deployment. You can save it as deploy.sh.

You’d just need to prepend it with an ssh command to your server ssh@your-domain.

php artisan route:clear
php artisan config:clear
git pull
php artisan migrate
composer install
php artisan route:cache
php artisan config:cache
php artisan optimize

The first two commands clear the route and configuration cache, which will then be generated again after pulling the new code. This will greatly improve the performance of your app when running in production.

Don’t forget that any configuration/routing change you make will not take effect until you clear the cache again.

Code Quality

Enforcing code quality helps the maintenance of big projects. You don’t want to end up with a terrible code base a few months from now. This is completely optional, but I’d recommend you set up some automated code quality tools.

We’re going to start by installing the necessary node modules:

npm install -g jshint jscs

EditorConfig

EditorConfig helps us maintain a consistent coding style between different editors and IDEs.
This is especially useful when you have many developers/contributors working on the same project.

You don’t want someone to push code with spaces instead of tabs, or vice versa, CRLF as line endings instead of LF, or vice versa.

Let’s create the.editorconfig file at the root level. Feel free to switch between CRLF and LF, tabs and spaces, etc.. it all depends on your coding style.

root = true

[*]
insert_final_newline = false
charset = utf-8
end_of_line = lf

[*.{js,html}]
indent_size = 4
indent_style = tab
trim_trailing_whitespace = true

[{package.json,bower.json,.jscs.json}]
indent_style = space
indent_size = 2

[*.sh]
end_of_line = lf

Depending on your code editor, you might need to download a plugin for editorConfig.

This is also convenient when you are working on more than 1 project using the same text editor and each project has different coding style guidelines.

JSHINT

Jshint is a Javascript code quality tool that helps to detect errors and potential problems in Javascript code.

It also enforces coding conventions on your team.

We need to create a .jshintrc file at the root level of the project.
You can browse the available options for jshint here.
Note that when you add a .jshintrc file, the angular task we have in our gulpfile will not recompile our code if it doesn’t validate according to jshint.

Here’s a recommended jshintrc for our scenario. Feel free to modify it according to your coding style.

{
  "browser": true,
  "bitwise": true,
  "immed": true,
  "newcap": false,
  "noarg": true,
  "noempty": true,
  "nonew": true,
  "maxlen": 140,
  "boss": true,
  "eqnull": true,
  "eqeqeq": true,
  "expr": true,
  "strict": true,
  "loopfunc": true,
  "sub": true,
  "undef": true,
  "globals": {
    "angular": false,
    "describe": false,
    "it": false,
    "expect": false,
    "beforeEach": false,
    "afterEach": false,
    "module": false,
    "inject": false
  }
}

JSCS

JSCS is a code style linter for programmatically enforcing your style guide.

We’re going to create a .jscs.json file at the root level.
Feel free to modify it depending on your style.

{
  "requireCurlyBraces": [
    "if",
    "else",
    "for",
    "while",
    "do"
  ],
  "requireSpaceAfterKeywords": [
    "if",
    "for",
    "while",
    "do",
    "switch",
    "return"
  ],
  "disallowSpacesInFunctionExpression": {
    "beforeOpeningRoundBrace": true
  },
  "disallowTrailingWhitespace": true,
  "disallowMixedSpacesAndTabs": true,
  "requireMultipleVarDecl": true,
  "requireSpacesInsideObjectBrackets": "all",
  "requireSpaceBeforeBinaryOperators": [
    "+",
    "-",
    "/",
    "*",
    "=",
    "==",
    "===",
    "!=",
    "!=="
  ],
  "disallowSpaceAfterPrefixUnaryOperators": [
    "++",
    "--",
    "+",
    "-"
  ],
  "disallowSpaceBeforePostfixUnaryOperators": [
    "++",
    "--"
  ],
  "disallowKeywords": [
    "with"
  ],
  "disallowKeywordsOnNewLine": [
    "else"
  ],
  "excludeFiles": ["node_modules/**"]
}

PHPCS

Just like jshint, we need to be able to enforce code cleanliness and consistency for our PHP files.

Let’s start by installing phpcs globally:

composer global require "squizlabs/php_codesniffer=*"

Now we can use the following command to check the app folder:

phpcs --standard=psr2 app/

The cool thing here is that we can use PSR2 as a coding standard which is used by Laravel, so we don’t have to set up a custom configuration file.

Git hooks

Jshint and jscs are great tools, but they need to be automatically enforced or else we’ll forget to lint our code.

You can optionally install the corresponding linter plugins for your text editor or IDE, but one of the recommended ways of doing it would be to run these linters as part of your git commit process.

Create .git/hooks/pre-commit if it does not exist and add the following:

#!/bin/sh
jscs angular/**/*.js
jshint angular/**/*.js
phpcs --standard=psr2 app/
exec 1>&2

Then run chmod +x .git/hooks/pre-commit.

Don’t forget to add this for each developer who joins your team, and every time someone pushes, they’ll be automatically warned of possible issues.

Conclusion

This article helped us set up a scalable Laravel and Angular Material app. You can also grab the source code of this tutorial on github: laravel5-angular-material-starter. If you’ve been using Angular with Bootstrap, I’d recommend you give Angular Material a try. Do you have any questions? I’d love to know what you think. Just let me know in the comments!

  • http://aziflaj.github.io/ Aldo Ziflaj

    It’s a very helpful article. Thanks!

    • Joubran

      I’m glad that you liked, thanks!

  • Antonio Cuan

    Hi Excellent Article !!!

    I have the following error

    TypeError: Cannot read property ‘registerWatcher’ of undefined

    in this line from the gulp.js file = elixir.config.registerWatcher(“default”, “angular/**”);

    Can you help me .Thanks

  • Morten

    Good written article. Im hoping to see more of these kinds of articles going this deeply. I a more interested in approaches which does not include material, just because I haven’t moved to material yet, but this looks very promising.

    • Joubran

      Thanks! You can actually just remove angular material and almost everything would work (except the UI – and don’t forget to remove the module from main.js)

  • Avi Alon

    Great article! Its very nice to see an article that takes you from start to finish and not just one isolated part of the project. Hope to see more like this.

    • Joubran

      Thanks! Feel free to open requests on the repository if you have issues or suggestions

  • Ido

    I had used the “gulp –production” on production in order to minify to js and css combined files.
    But it looks like it doesn’t work on your way.
    There is a way to implement it?

    • Joubran

      So what was the issue when you ran “gulp –production”?
      Was there any error?

      • Ido

        There is no error, but the files are not minified !

  • Morten

    Solution 1 did not work for me, but i got an empty elixir object so it could not find the config

    • Joubran

      @disqus_qK7CCbU56d:disqus I’m going to publish a fix for this on the article itself during the weekend. Stay tuned!

      • Morten

        I solved all my issues by throwing it all out and used your starting repo.
        I assume that some changes with elixir is that you made your own angular and bower adapter instead of the provided angular adapter mentioned in this article.

        If you are gonna update this article (or create a new), could you do some more explaining on the elixir tasks in your repo? I do see what it does, but its all new so yeah :p If you dont want to go too deep into it, mainly because this is a php article, not js, i’ll understand, and google is also a good friend lf mine.

        As a last request, do you mind reply me when the article is updated? Then i’ll check it out as quick as i can!

        • Joubran

          Yes I’m going to update the article this weekend. Honestly the good thing about it is that you don’t have to worry about the underlying implementation – same philosophy as Elixir. But I’ll explain briefly :D
          And yes, I’ll ping you as soon as I update it
          Cheers!

          • http://www.afaraz.it/ Antonis Farangitakis

            @joubran:disqus have you updated the article?

          • Jad Joubran

            @afarazit:disqus yes

        • Joubran

          Updated the article!
          Let me know what you think

          • Jonathan

            I ran into this same issue and downgrading to Elixir 2 did not resolve it. I’m going to back-track and adjust based on your updates to the article, which I imagine will resolve it. Thanks again for the thorough and well-written article. Helpful stuff!

          • Joubran

            Awesome!
            Let me know if you have any issues

  • Ido

    I took the gulpfile from the repo and it works, I’ll find out what’s the difference myself. Thanks!

  • Ido

    Where do you suggest to put images of a directive? in it’s folder?

    • Joubran

      I guess the only out-of-the-box option right now, is inside `public/` folder – maybe create an `images` subfolder

  • Skattabrain

    I think your project kicks ass! I just downloaded and ran it… cool stuff!

    • Joubran

      Awesome, thanks :D

  • Dwi Marstya Wibowo

    Very good article, but i have some error like this :

    D:xampphtdocsmyapp>gulp && gulp watch

    C:Usersdwi.wibowoAppDataRoamingnpmnode_modulesgulpnode_modulessemversemver.js:281
    throw new TypeError(‘Invalid Version: ‘ + version);
    ^
    TypeError: Invalid Version: undefined at new SemVer (C:Usersdwi.wibowoAppDataRoamingnpmnode_modulesgulpnode_modulessemversemver.js:281:11)

    Need Help, thanks before

  • Shashikant Maharshi

    Thanks Joubran :) it’s really an awesome & handy tutorial. Keep shining.

    • Ruslan Savitskij

      Hi Shashikant,

      I notice you are interested in Jad´s model and I wonder whether you could be interested in doing some freelance job based on this model.

      I case you do – pls let me know and also pls mention your demand per hour ( in the end it is always fixed price, this is just a way for me to understand general level of demand

      Best regards

  • danconiaus

    Why not just use Laravel as the API with Angular on the front end? Wouldn’t that scale much better?

    • Jad Joubran

      Hi @danconiaus:disqus
      Yes this is how the article is doing it.. we’re using Laravel for the API and angular for the front-end

  • rolfen

    More is better? I don’t think so. Forgive my bluntness but all I see is hours worth of work (at best – assuming I’ll just be pasting stuff without understanding, and something is bound to go wrong somewhere, at some point), megabytes of code, and I haven’t seen a single functionality yet. I’m sure it brings a lot to your environment, but will I need all this? I don’t know.
    I just feel you’re having a framework-fest here and it’s not very appealing to me. I often not use any frameworks at all and it’s OK and actually faster and more rewarding most of the times, in my experience.
    Of course not always, but I deal with the problem when I get there.
    Thank you for your article.

    • jadjoubran

      Hi @rolfen:disqus
      Thank you so much for your comment
      It really depends on personal preference and experience.. when I do consultancy for startups, sometimes I don’t recommend the use of Angular in particular cases..
      but personally, when I use this stack, I am much more productive..
      Cheers

      • rolfen

        Thank you. I am glad that you respect my opinion and understand that it depends on who is doing what. If you say that are more productive, then it’s hard to go against your judgement. After all you are best placed to know what is best for you and for a task you’re responsible for.
        I like (and believe in) less source code, less wasted CPU/memory, less things to learn, less things that can go wrong, and I enjoy the challenge of using a small subset of the language but using it well, and carefully building upon that.
        But hey that’s just me and I’m just a human being like you.

  • Boris DEHOUMON

    Thanks you!! Its very very helpful. That amazing!!!!!!!

  • Sidik Saepudin

    @jad@joubran:disqus very good article, but i want to ask something how can i add module in angular? for example if i want to add module angular-bootstrap, what is the right thing to do to add that module?
    also how can i add controller in angular on this repo?
    Thansk

    • jadjoubran

      You need to install it by using `bower install` with the `–save` flag and then add it to your main module

      • Sidik Saepudin

        thanks for your reply @jadjoubran:disqus but i have another question about how to add specific controller to specifig part or page. For example i’ve created page with ‘artisan ng:page somepage’ and i want to add controller to that page, how can i do that?
        Thansk

        • jadjoubran

          In the newest version, we don’t recommend the user of controllers anymore.. which version are you running on?
          You can create a controller manually and adding it to `ui-router`

  • stevebrowndotco

    Shouldn’t the NPM dependencies be in devDependencies?

  • Hélio Miranda

    This article is great! Thanks a lot!

  • Ody Light

    Pls! Could you update this handy tutorial?

  • greatune

    hi, i haved use this stack for some time, the only most problem i faced is the url #,
    in html i add
    ,in the config file i add set html5.mode true, then i restart,
    the url working well from http:8000/#/home -> http:8000/home,
    However, when u copy the link to new tab, it show page not found by laravel, it is because it detect the route by laravel in laravel routes, if you refresh the page/reload again, it show page not found, i get annoyed by the # in url, so how to fix this , thanks

Recommended

Learn Coding Online
Learn Web Development

Start learning web development and design for free with SitePoint Premium!

Get the latest in PHP, once a week, for free.