Tổng quan

Angularjs là một JS Framework nổi tiếng của Google. Nó được ứng dụng rất nhiều trong các dự án web hiện nay. Angular có rất nhiều tính năng và ưu điểm tuyệt vời. Bạn muốn áp dụng ngay vào dự án của mình. Điều đầu tiên cần làm là tìm hiểu cấu trúc của nó.

Angularjs có khá nhiều lựa chọn để cấu trúc project của bạn như:

  • Chuẩn MVC: cấu trúc theo thành phần của Angularjs như controllers, directives, services, views ...
    app/
    ----- controllers/
    ---------- login.controller.js
    ---------- user.controller.js
    ---------- post.controller.js
    ----- directives/
    ---------- main.directive.js
    ---------- user.directive.js
    ----- services/
    ---------- user.service.js
    ---------- post.service.js
    ----- app.js
    ------views/
    ---------- login.html
    ---------- user.html
    ---------- index.html
  • Cấu trúc theo module: cấu trúc thư mục theo các feature của dự án. Mỗi feature được đặt trong một folder riêng. Ví du: login, users, products, posts ...
    app/
    ----- core/
    ---------- core.module.js
    ---------- core.service.js
    ----- login/
    ---------- login.controller.js
    ---------- login.service.js
    ----- users/
    ---------- user.controller.js
    ---------- user.directive.js
    ----- posts/
    ---------- post.controller.js
    ---------- post.config.js
    ----- app.route.js
    ----- app.module.js

Theo mình cấu trúc theo module ưu biệt hơn hẳn. Nó có tính mở rộng, cấu trúc rõ ràng, dễ bảo trì, nhất là các dự án lớn; hiện được các kĩ sư Google khuyến khích.

Xây dựng ứng dụng

Cấu trúc ứng dụng

Chúng ta sẽ làm một ứng dụng nhỏ có các chức năng:

  • Đăng nhập
  • Landing page

Ở đây mình dùng bower để quản lý các thư viện JS. Các bước cài đặt các bạn có thể tham khảo tại đây

Tạo thư mục của project có cấu trúc như sau:

  • mkdir angular
  • cd angular
angular
---- app/
---- bower_components/
---- bower.json
---- data/
---- .htaccess
---- index.html

Giải thích:

  • app: thư mục chứa tất cả code angularjs
  • bower_components: thư mục chứa các thư viện được cài đặt thông qua bower từ file config bower.json
  • bower.json có file khai báo các thư viện cần thiết cho dự án. Nọi dung như sau:
    // bower.json
    {
      "name": "angularjs-test",
      "description": "",
      "main": "",
      "authors": [
        "Nguyen Quoc Dat <[email protected]>"
      ],
      "license": "MIT",
      "homepage": "",
      "ignore": [
        "**/.*",
        "node_modules",
        "bower_components"
      ],
      "dependencies": {
        "angular": "1",
        "angular-ui-router": "ui-router#^0.3.1",
        "bootstrap": "^3.3.7",
        "ngstorage": "^0.3.11"
      }
    }
- Để cài đặt các thư viện JS cần thiết, chúng ta sẽ cài các vendor sau: `angular` version `1`, `ui-router`, `ngstorage`.
- Run command sau trên terminal:
    - `bower install`
  • data: Thư mục chứa các dữ liệu để được gọi từ api.
  • .htaccess: File rewrite url cho appache để tạo friendly url trong ui-router
    // .htaccess
    <IfModule mod_rewrite.c>
    Options +FollowSymlinks
    RewriteEngine On
    # Don't rewrite files or directories
    RewriteCond %{REQUEST_FILENAME} -f [OR]
    RewriteCond %{REQUEST_FILENAME} -d
    RewriteRule ^ - [L]

    RewriteRule (.*) /index.html [L]
    </IfModule>
  • index.html: File index của ứng dụng
     <!DOCTYPE html>
    <html lang="en" ng-app="app">
    <head>
        <base href="/">
            <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.7/css/bootstrap.min.css"
            integrity="sha384-BVYiiSIFeK1dGmJRAkycuHAHRg32OmUcww7on3RYdg4Va+PmSTsz/K68vbdEjh4u" crossorigin="anonymous">
        </head>
        <body>

        <div class="container">
            <nav class="navbar navbar-default" ng-include="'app/header/header.html'">
            </nav>
                <div ui-view></div>
            </div>
        </div>

        <script src="bower_components/angular/angular.min.js"></script>
        <script src="bower_components/angular-ui-router/release/angular-ui-router.min.js"></script>
        <script src="bower_components/ngstorage/ngStorage.min.js"></script>

        <script src="app/core/core.module.js"></script>
        <script src="app/core/auth.service.js"></script>

        <script src="app/header/header.module.js"></script>
        <script src="app/header/header.controller.js"></script>

        <script src="app/login/login.module.js"></script>
        <script src="app/login/login.config.js"></script>
        <script src="app/login/login.controller.js"></script>

        <script src="app/landing/landing.module.js"></script>
        <script src="app/landing/landing.config.js"></script>
        <script src="app/landing/landing.controller.js"></script>

        <script src="app/app.module.js"></script>
        <script src="app/app.config.js"></script>

    </body>
    </html>

Tạo các file và thư mục như sau trong thư mục app.

         angular
         ---- app
         -------- app.config.js
         -------- app.module.js
         -------- core/
         -------------core.module.js
         -------------auth.service.js
         -------- header/
         -------------header.module.js
         -------------header.controller.js
         -------- landing/
         -------------landing.module.js
         -------------landing.config.js
         -------------landing.controller.js
         -------- login/
         -------------login.module.js
         -------------login.config.js
         -------------login.controller.js

Giải thích:

  • app.module.js: module chính của ứng ứng dụng.
    (function() {
    "use strict";
        angular.module('app', ['app.core', 'app.header', 'app.landing', 'app.login']);
    })();
  • app là module chính của ứng dụng, dependency inject các module con app.core, app.header, app.login.

  • app.core là module core của ứng dụng, nó sẽ load các dependency vendor của toàn bộ ứng dụng như ui.router, ngStorage...

    // core.module.js
     (function() {
         'use strict';
         angular.module('app.core', ['ui.router', 'ngStorage']);
     })();
  • app.config.js: file config chính của ứng dụng:
    // app.config.js
    (function () {
        'use strict';

        angular
            .module('app')
            .config(configure)
            .run(runBlock);

        configure.$inject =  ['$locationProvider'];
        function configure($locationProvider) {
            $locationProvider.html5Mode(true);
        }

        runBlock.$inject = ['$http', '$rootScope', '$localStorage', '$state', 'AuthService'];
        function runBlock( $http, $rootScope, $localStorage, $state, AuthService) {

                if ($localStorage.currentUser) {
                    $http.defaults.headers.common.Authorization = 'Bearer ' + $localStorage.currentUser.token;
                    AuthService.updateCurrentUser($localStorage.currentUser.token, $localStorage.currentUser.username);
                }

                $rootScope.$on('$stateChangeStart',
                    function(event, toState, toParams, fromState, fromParams, options){
                        if (toState.name == 'login' || toState.name == 'landing') {

                        } else {
                            if (!AuthService.isAuthenticated()) {
                                event.preventDefault();
                                $state.go('login');
                            }
                        }
                });
            }
    })();

Chúng ta sẽ config url theo html5Mode để tạo pretty url. AuthService: là service xử lý và lưu trữ thông tin login của user, token. Mình sẽ sử dụng $localStorage để lưu trữ các thông tin này thay vì sử dụng cookie.

  • auth.service.js:
          (function() {
            'use strict';
            angular.module('app.core').factory('AuthService', AuthService);
            AuthService.$inject = ['$http', '$localStorage'];

            function AuthService($http, $localStorage) {
                var authService = {
                    currentUser: {
                        token: null,
                        username: null
                    },
                    login: login,
                    logout: logout,
                    updateCurrentUser: function(token, username) {
                         this.currentUser.token = token;
                         this.currentUser.username = username;
                    },
                    isAuthenticated: function() {
                        return this.currentUser.token ? true : false;
                    }
                }
                return authService;

                function login(email, password) {
                    return $http.post('/data/login.json', {
                        email: email,
                        password: password
                    }).then(function(data, status, headers, config) {
                        var res = data.data;
                        if (res.status) {
                            $http.defaults.headers.common.Authorization = 'Bearer ' + res.data.token;
                            $localStorage.currentUser = {
                                username: res.data.name,
                                token: res.data.token
                            };
                            authService.currentUser.token = res.data.token;
                            authService.currentUser.username = res.data.name;
                        }
                        return;
                    });
                }

                function logout() {
                    $http.defaults.headers.common.Authorization = null;
                    $localStorage.currentUser = null;
                    authService.currentUser.token = null;
                    authService.currentUser.username = null;
                    return;
                }
            }
        })()
  • AuthService có các thuộc tính thông tin đăng nhập currentUser, login, logout. Khi user login sẽ api login với email, password. Ở đây để đơn giản mình sẽ tạo ra các data mẫu json ở thư mục data. Ví dụ: login login.json, danh sách user users.json giả lập kết quả trả về từ api.
        // login.json
        {
            "status": true,
            "data": {
                "token": "xxx",
                "id": 1,
                "name": "datnq"
            }
        }

Như vậy khi login thành công mình sẽ lưu trữ thông tin của user vào localStorage. Khi logout thì mình sẽ xóa thông tin của user trong localStorage.

Module app.login

  1. login.module.js
// login.module.js
 (function() {
     'use strict';
     angular.module('app.login', ['app.core']);
 })();

Chú ý app.login sẽ inject module core của ứng dụng app.core 2. login.config.js để config route login

(function() {
    'use strict';
    angular.module('app.login').config(config);

    function config($stateProvider, $urlRouterProvider) {
        $urlRouterProvider.otherwise("/landing");
        $stateProvider.state('login', {
            url: "/login",
            templateUrl: "app/login/login.html",
            controller: "LoginController",
            controllerAs: "vm"
        })
    }
})();
  1. login.controller.js: Controller login sẽ inject AuthService.
(function() {
	'use strict';

	angular
		.module('app.login')
		.controller('LoginController', LoginController);

	LoginController.$inject = ['$state', '$http', 'AuthService'];
	function LoginController ($state, $http,  AuthService) {
		var vm = this;
		vm.email = '';
		vm.password = '';
		vm.login  = login;

		function login() {
			AuthService
					.login(vm.email, vm.password)
					.then(
						function(data, status, headers, config) {
							if (AuthService.isAuthenticated)
							    return $state.go('landing');
						},
						function (error) {
							alert(error);
						}
					);
		};

	}
})();

Chúng tiếp tục xây dựng chức năng landing page.

Module app.landing

  1. landing.module.js: khởi tạo module app.landing. Ở đây module app.landing sẽ inject module chung app.core của ứng dụng.
    // landing.module.js
    (function() {
        'use strict';
        // angular
        angular.module('app.landing', ['app.core']);
    })();
  1. landing.config.js: config url route cho trang landing page
    (function() {
        'use strict';

        angular
            .module('app.landing')
            .config(config);

        config.$inject = ['$stateProvider', '$urlRouterProvider'];
        function config($stateProvider, $urlRouterProvider) {
            $stateProvider
                .state('landing', {
                    url: "/landing",
                    templateUrl: "app/landing/landing.html",
                    controller: "LandingController",
                    controllerAs: "vm"
                })
        }
    })();
  1. landing.controller.js: Controller landing page. Mình sẽ check nếu user login rồi sẽ hiển thị câu chào. Nếu chưa sẽ hiển thị yêu cầu login. AuthService.currentUser() sẽ get thông tin của user login.
  • landing.html: view landing
    <div class="panel panel-default" ng-controller="LandingController as vm">
        <div class="panel-heading">
            <h3 class="panel-title">
                Landing page
            </h3>
        </div>
        <div class="panel-body">
            Angularjs structure
            <div ng-if="vm.currentUser.username">
            Hello <b> {{vm.currentUser.username}} </b>!
            </div>
             <div ng-if="!vm.currentUser.username">
            Hello guess, please login!
            </div>
        </div>
    </div>

Module app.header

Module này để xử lý phần header bar. Phần này tương tự phần landing.

  1. header.module.js
    (function() {
        'use strict';
        angular.module('app.header', ['app.core']);
    })();
  1. header.controller.js: header controller
    (function() {
        'use strict';

        angular
		.module('app.header')
		.controller('HeaderController', HeaderController);

        HeaderController.$inject = ['AuthService', '$state'];
        function HeaderController (AuthService, $state) {

            var vm = this;
            vm.currentUser = AuthService.currentUser;

            vm.logout = logout;

            function logout() {
                AuthService.logout();
                $state.go('landing');
            }
        }
    })();
  1. header.html: header view
    <!-- Collect the nav links, forms, and other content for toggling -->
    <div class="collapse navbar-collapse" id="bs-example-navbar-collapse-1" ng-controller="HeaderController as vm">
        <ul class="nav navbar-nav">
            <li>
                <a ui-sref="landing">
                    Home
                </a>
            </li>
            <li ng-if="vm.currentUser.username">
                <a>
                    Hello
                    <b>
                        {{vm.currentUser.username}}
                    </b>
                </a>
            </li>
            <li ng-if="vm.currentUser.username">
                <a href="#" ng-click="vm.logout();">
                    Logout
                </a>
            </li>
            <li ng-if="!vm.currentUser.username">
                <a ui-sref="login">
                    Login
                </a>
            </li>
        </ul>
    </div>
    <!-- /.navbar-collapse -->

Mở trình duyệt lên và xem thành quả của chúng ta nào:

  • Chưa login:

login-fail

  • Login thành công:

login-success

Kết luận

  • Như vậy chúng ta đã xây dựng và cấu trúc được một ứng dụng Angularjs tốt, một cấu trúc rõ ràng, dễ mở rộng, bảo trì và phát triển. Phần 2 mình sẽ tích hợp socket.io để xây dựng một ứng dụng chát realtime bằng NodeJs, socket.ioAngularjs.

  • Toàn bộ source code bạn có thể download tại đây