angularjs coffeescript javascript front-end - It costs 7 mins to read

Khi quay lại với nghiệp Web Developer sau hơn 4 năm ra trường và gắn bó với công việc của một Data Processing trong lĩnh vực nghiên cứu thị trường. Số phận đưa đẩy thế nào mà mình phải học và làm việc với framework bên Front-End là AngularJS. Tuy vậy, sau thời gian dài làm việc, mình vẫn chưa hệ thống hóa kiến thức AngularJS của mình một cách bài bản lắm. Ngẫm lại, mình chỉ sử dụng nhiều đến module, $scope, controller, directive, một ít service và các thể loại, dòng họ nhà ng-xxx (ng-app, ng-if, ng-show, ng-init, ng-click, ng-class…) trong AngularJS thôi chứ ít đụng đến các chức năng cao cấp hơn, cũng không tuân theo mô hình MVVM và tổ chức cấu trúc File một cách hợp lý và cũng không viết ứng dụng dạng Single Page. Đó thật là thiếu sót của mình khi không thể khai thác hết sức mạnh của AngularJS. Mình ấp ủ thực hiện loạt bài này đã lâu và đây là lúc bắt tay vào hành động. (Và hy vọng hoàn thành nó trước khi nó kịp lỗi thời vì AngularJS 2.0 sắp xuất hiện.)

Nói thật, Mình quen thuộc AngularJS còn hơn cả jQuery, quen viết bằng CoffeeScript còn hơn cả viết bằng JavaScript. Do đó, hầu hết các ví dụ trong bài đều sử dụng CoffeeScript.

Bài hôm nay giúp liệt kê các loại Provider mà AngularJS cung cấp cho chúng ta: ==constant, value, service, factory, decorator, provider==. Hiểu rõ sự khác biệt giữa chúng, bạn sẽ biết tùy nghi sử dụng chúng sao cho phù hợp với mục đích của mình. (Mình nhớ một câu nói, nếu bạn phải sử dụng đến $rootScope để chia sẻ Scope giữa các Controller, hay tệ hơn là không biết chia sẻ dữ liệu giữa các Controller với nhau. Vậy là bạn đang đi sai cách rồi đấy)

Các dịch vụ này đều được cung cấp bởi $provide và bạn có thể dễ dàng inject chúng để sử dụng vào các Controller, Directive. (Dependency Injection là một trong những điểm mạnh của AngularJS - Nếu chưa biết DI là gì, bạn có thể tự tìm hiểu thêm, Mình cũng có dự định sẽ viết một bài về vấn đề này trong Loạt bài tìm hiểu về AngularJS.)

Constant (Hằng số)

Hằng số có thể được inject vào bất cứ đâu. Một Hằng số không thể thay đổi giá trị sau khi nó đã được khởi tạo

Khởi tạo hằng số

var app = angular.module('app', []);
app.constant('movieTitle', 'The Matrix');
app.controller('ctrl', function (movieTitle) {
  expect(movieTitle).toEqual('The Matrix');
});

Một cách khởi tạo khác (thông qua $provide)

app.config(function ($provide) {
  $provide.constant('movieTitle', 'The Matrix');
});
Value

Khác với Hằng số, Value (Giá trị) có thể bị thay đổi sau khi đã khởi tạo. Value có thể là một chuỗi, một số hay một hàm

Khởi tạo Value

var app = angular.module('app', []);
app.value('movieTitle', 'The Matrix');
app.controller('ctrl', function (movieTitle) {
  expect(movieTitle).toEqual('The Matrix');
});

Một cách khởi tạo khác (Thông qua $provide)

app.config(function ($provide) {
  $provide.value('movieTitle', 'The Matrix')
});
Decorator

Decorator là một mẫu thiết kế được dùng để thay đổi giá trị hoặc bao đóng các Provider khác. Trừ trường hợp ngoại lệ là Decorator không can thiệp được Constant.

var app = angular.module('app', []);
app.value('movieTitle', 'The Matrix');
app.config(function ($provide) {
  $provide.decorator('movieTitle', function ($delegate) {
    return $delegate + ' - starring Keanu Reeves';
  });
});
app.controller('myController', function (movieTitle) {
  expect(movieTitle).toEqual('The Matrix - starring Keanu Reeves');
});
Service

Service là một Injectable Constructor. Khi inject vào một controller nghĩa là bạn đã inject một instance của các function có trong Service đó vào controller.

Service là cách tốt nhất để giao tiếp và chia sẻ dữ liệu giữa các Controller với nhau.

Khởi tạo Service

var app = angular.module('app' ,[]);
 
app.service('movie', function () {
  this.title = 'The Matrix';
});
 
app.controller('ctrl', function (movie) {
  expect(movie.title).toEqual('The Matrix');
});

Cách khác để khởi tạo Service

app.config(function ($provide) {
  $provide.service('movie', function () {
    this.title = 'The Matrix';
  });
});
Factory

Factory là một injectable function. Thoạt nhìn thì nó khá giống Service nhưng điểm khác nhau chính giữa chúng là Factory Inject một plain function còn Service inject contructor. Ứng dụng của Factory: Could be useful for returning a ‘class’ function that can then be new’ed to create instances.

Có vẻ khó hiểu nhỉ, vô tư đi, không sao cả. Chúng ta sẽ xem qua cách khởi tạo một Factory và sau đó bằng nhiều ví dụ, chúng ta sẽ thấy được sự khác biệt giữa Factory và Service cũng như Factory và Provider.

Khởi tạo Factory

var app = angular.module('app', []);
 
app.factory('movie', function () {
  return {
    title: 'The Matrix';
  }
});
 
app.controller('ctrl', function (movie) {
  expect(movie.title).toEqual('The Matrix');
});

Cách khác để khởi tạo Factory

app.config(function ($provide) {
  $provide.factory('movie', function () {
    return {
      title: 'The Matrix';
    }
  });
});
Provider

Provider là trường hợp phức tạp nhất (và cũng mạnh mẽ nhất) trong số các Provider, Nó giống Factory nhưng cho phép bạn cấu hình bằng các configuration option (Nói nôm na Provider là một Configurable Factory).

Khởi tạo Provider

var app = angular.module('app', []);
 
app.provider('movie', function () {
  var version;
  return {
    setVersion: function (value) {
      version = value;
    },
    $get: function () {
      return {
          title: 'The Matrix' + ' ' + version
      }
    }
  }
});
 
app.config(function (movieProvider) {
  movieProvider.setVersion('Reloaded');
});
 
app.controller('ctrl', function (movie) {
  expect(movie.title).toEqual('The Matrix Reloaded');
});
Service vs Factory vs Provider

Một ví dụ hoàn chỉnh để thấy được sự khác nhau giữa Service - Factory - Provider:

var app = angular.module( 'app', [] );
var MyFunc = function() {
  this.name = "default name";
  this.$get = function() {
    this.name = "new name"
    return "Hello from MyFunc.$get(). this.name = " + this.name;
  };
  return "Hello from MyFunc(). this.name = " + this.name;
};

Bây giờ ta có một hàm MyFunc đã khởi tạo trước, sẽ ra sao nếu Ta khởi tạo Service, Factory và Provider bằng hàm MyFunc này

// returns the actual function
app.service( 'myService', MyFunc );
// returns the function's return value
app.factory( 'myFactory', MyFunc );
// returns the output of the function's $get function
app.provider( 'myProv', MyFunc );

Kết quả là

function MyCtrl( $scope, myService, myFactory, myProv ) {
  $scope.serviceOutput = "myService = " + myService;
  $scope.factoryOutput = "myFactory = " + myFactory;
  $scope.providerOutput = "myProvider = " + myProv;
}
myService = [object Object] 
myFactory = Hello from MyFunc(). this.name = default name 
myProvider = Hello from MyFunc.$get(). this.name = new name

Một ví dụ khác

provide.value('a', 123);
function Controller(a) {
  expect(a).toEqual(123);
}

Ở đây, chúng ta khởi tạo một value là a với giá trị 123, Nhưng nếu bạn muốn tính toán giá trị đó? Đây là lúc sử dụng Factory

provide.factory('b', function(a) {
  return a*2;
});

function Controller(b) {
  expect(b).toEqual(246);
}

Bây giờ, bạn có một Lớp Greeter

function Greeter(a) {
  this.greet = function() {
    return 'Hello ' + a;
  }
}

Và nếu sử dụng Factory cho Greeter này, bạn sẽ phải viết như sau:

provide.factory('greeter', function(a) {
  return new Greeter(a);
});

Sử dụng trong Controller

function Controller(greeter) {
  expect(greeter instanceof Greeter).toBe(true);
  expect(greeter.greet()).toEqual('Hello 123');
}

Rườm rà nhỉ, nếu sử dụng Service cho mục đích này, bạn sẽ thấy code sẽ clean hơn mà kết quả vẫn tương tự:

provider.service('greeter', Greeter);

Nhưng nếu muốn cấu hình lớp Greeter trước khi inject, bạn phải sử dụng Provider:

provide.provider('greeter2', function() {
  var salutation = 'Hello';
  this.setSalutation = function(s) {
    salutation = s;
  }

  function Greeter(a) {
    this.greet = function() {
      return salutation + ' ' + a;
    }
  }

  this.$get = function(a) {
    return new Greeter(a);
  };
});

Và có thể cấu hình chúng (thiết lập lại salutation) như sau:

angular.module('abc', []).config(function(greeter2Provider) {
  greeter2Provider.setSalutation('Halo');
});

function Controller(greeter2) {
  expect(greeter2.greet()).toEqual('Halo 123');
}

Tóm tắt