Nơi để đặt dữ liệu mô hình và hành vi? [tl; dr; Sử dụng dịch vụ]


341

Tôi đang làm việc với AngularJS cho dự án mới nhất của tôi. Trong tài liệu và hướng dẫn, tất cả dữ liệu mô hình được đưa vào phạm vi điều khiển. Tôi hiểu rằng phải có sẵn cho bộ điều khiển và do đó trong các khung nhìn tương ứng.

Tuy nhiên tôi không nghĩ rằng mô hình thực sự nên được thực hiện ở đó. Nó có thể phức tạp và có các thuộc tính riêng chẳng hạn. Hơn nữa, người ta có thể muốn sử dụng lại nó trong bối cảnh / ứng dụng khác. Đưa mọi thứ vào bộ điều khiển hoàn toàn phá vỡ mô hình MVC.

Điều tương tự cũng đúng với hành vi của bất kỳ mô hình. Nếu tôi sử dụng kiến trúc DCI và tách biệt hành vi khỏi mô hình dữ liệu, tôi sẽ phải giới thiệu các đối tượng bổ sung để giữ hành vi. Điều này sẽ được thực hiện bằng cách giới thiệu vai trò và bối cảnh.

DCI == D ata C ollaboration I nteraction

Tất nhiên dữ liệu mô hình và hành vi có thể được thực hiện với các đối tượng javascript đơn giản hoặc bất kỳ mẫu "lớp" nào. Nhưng cách AngularJS sẽ làm là gì? Sử dụng dịch vụ?

Vì vậy, nó đi xuống câu hỏi này:

Làm thế nào để bạn thực hiện các mô hình tách rời khỏi bộ điều khiển, theo các thực tiễn tốt nhất của AngularJS?


12
Tôi sẽ bỏ phiếu cho câu hỏi này nếu bạn có thể xác định DCI hoặc ít nhất cung cấp mẫu đánh vần. Tôi chưa bao giờ thấy từ viết tắt này trong bất kỳ tài liệu phần mềm. Cảm ơn.
Jim Raden

13
Tôi chỉ thêm một liên kết cho DCI làm tài liệu tham khảo.
Nils Blum-Oeste

1
@JimRaden DCI là Dataq, Ngữ cảnh, tương tác và là một mô hình được xây dựng trước tiên bởi cha đẻ của MVC (Trygve Reenskauge). Bây giờ có khá nhiều rác rưởi về chủ đề này. Một cuốn sách đáng đọc là Coplien và Bjørnvig "Kiến trúc tinh gọn"
Rune FS

3
Cảm ơn. Dù tốt hay xấu, hầu hết mọi người thậm chí còn không biết về văn học gốc. Có 55 triệu bài viết về MVC, theo Google, nhưng chỉ 250.000 bài viết đề cập đến MCI và MVC. Và trên Microsoft.com? 7. AngularJS.org thậm chí không đề cập đến từ viết tắt DCI: "Tìm kiếm của bạn - trang web: angularjs.org dci - không khớp với bất kỳ tài liệu nào".
Jim Raden

Các đối tượng tài nguyên về cơ bản là các mô hình trong Angular.js .. đang mở rộng chúng.
Salman von Abbas

Câu trả lời:


155

Bạn nên sử dụng các dịch vụ nếu bạn muốn một cái gì đó có thể sử dụng được bởi nhiều bộ điều khiển. Đây là một ví dụ đơn giản:

myApp.factory('ListService', function() {
  var ListService = {};
  var list = [];
  ListService.getItem = function(index) { return list[index]; }
  ListService.addItem = function(item) { list.push(item); }
  ListService.removeItem = function(item) { list.splice(list.indexOf(item), 1) }
  ListService.size = function() { return list.length; }

  return ListService;
});

function Ctrl1($scope, ListService) {
  //Can add/remove/get items from shared list
}

function Ctrl2($scope, ListService) {
  //Can add/remove/get items from shared list
}

23
Điều gì sẽ có lợi khi sử dụng một dịch vụ so với việc chỉ tạo một đối tượng Javascript đơn giản làm mô hình và gán điều này cho phạm vi điều khiển?
Nils Blum-Oeste

22
Trong trường hợp bạn cần cùng một logic được chia sẻ giữa nhiều bộ điều khiển. Ngoài ra, cách này dễ dàng hơn để kiểm tra mọi thứ một cách độc lập.
Andrew Joslin

1
Ví dụ cuối cùng của loại hút, cái này có ý nghĩa hơn. Tôi đã chỉnh sửa nó.
Andrew Joslin

9
Vâng, với một đối tượng Javascript cũ, bạn sẽ không thể đưa bất cứ thứ gì Angular vào ListService của bạn. Giống như trong ví dụ này, nếu bạn cần $ http.get để lấy dữ liệu Danh sách khi bắt đầu hoặc nếu bạn cần tiêm $ rootScope để bạn có thể phát các sự kiện $.
Andrew Joslin

1
Để làm cho ví dụ này thêm DCI như dữ liệu không nên nằm ngoài ListService?
PiTheNumber

81

Tôi hiện đang thử mẫu này, mặc dù không phải DCI, cung cấp dịch vụ tách mẫu / dịch vụ cổ điển (với các dịch vụ để nói chuyện với các dịch vụ web (còn gọi là CRUD mô hình) và mô hình xác định các thuộc tính và phương thức của đối tượng).

Lưu ý rằng tôi chỉ sử dụng mẫu này bất cứ khi nào đối tượng mô hình cần các phương thức làm việc trên các thuộc tính của chính nó, mà tôi có thể sẽ sử dụng ở mọi nơi (chẳng hạn như getter / setters được cải tiến). Tôi không ủng hộ việc làm này cho mọi dịch vụ một cách có hệ thống.

EDIT: Tôi đã từng nghĩ rằng mô hình này sẽ đi ngược lại câu thần chú "Mô hình góc cạnh là đối tượng javascript cũ đơn giản", nhưng dường như với tôi bây giờ mô hình này hoàn toàn ổn.

EDIT (2): Để rõ ràng hơn nữa, tôi chỉ sử dụng lớp Model để tính các getters / setters đơn giản (ví dụ: được sử dụng trong các mẫu xem). Đối với logic kinh doanh lớn, tôi khuyên bạn nên sử dụng (các) dịch vụ riêng biệt "biết" về mô hình, nhưng được tách biệt khỏi chúng và chỉ bao gồm logic nghiệp vụ. Gọi nó là lớp dịch vụ "chuyên gia kinh doanh" nếu bạn muốn

service / ElementService.js (chú ý cách Element được chèn trong khai báo)

MyApp.service('ElementServices', function($http, $q, Element)
{
    this.getById = function(id)
    {
        return $http.get('/element/' + id).then(
            function(response)
            {
                //this is where the Element model is used
                return new Element(response.data);
            },
            function(response)
            {
                return $q.reject(response.data.error);
            }
        );
    };
    ... other CRUD methods
}

model / Element.js (sử dụng angularjs Factory, được tạo để tạo đối tượng)

MyApp.factory('Element', function()
{
    var Element = function(data) {
        //set defaults properties and functions
        angular.extend(this, {
            id:null,
            collection1:[],
            collection2:[],
            status:'NEW',
            //... other properties

            //dummy isNew function that would work on two properties to harden code
            isNew:function(){
                return (this.status=='NEW' || this.id == null);
            }
        });
        angular.extend(this, data);
    };
    return Element;
});

4
Tôi mới vào Angular, nhưng tôi tò mò muốn biết liệu tại sao các cựu chiến binh lại nghĩ đây là dị giáo. Đây có lẽ là cách ban đầu tôi cũng sẽ tiếp cận nó. Ai đó có thể cung cấp một số thông tin phản hồi?
Aaronius

2
@Aaronius chỉ cần rõ ràng: tôi chưa bao giờ thực sự đọc "bạn không bao giờ nên làm điều đó" trên bất kỳ tài liệu hay blog nào của angularjs, nhưng tôi luôn đọc những thứ như "angularjs không cần một mô hình, nó chỉ sử dụng javascript cũ" và tôi phải tự mình khám phá mô hình này. Vì đây là dự án thực sự đầu tiên của tôi trên AngularJS, tôi đang đưa ra những cảnh báo mạnh mẽ đó, để mọi người không sao chép / dán mà không suy nghĩ trước.
Ben G

Tôi đã giải quyết trên một mô hình gần như tương tự. Thật xấu hổ khi Angular không có bất kỳ sự hỗ trợ thực sự nào (hoặc dường như mong muốn hỗ trợ) một mô hình theo nghĩa "cổ điển".
drt

3
Điều đó không có vẻ dị giáo đối với tôi, bạn đang sử dụng các nhà máy cho những gì chúng được tạo ra: xây dựng các đối tượng. Tôi tin rằng cụm từ "angularjs không cần mô hình" có nghĩa là "bạn không cần thừa kế từ một lớp đặc biệt hoặc sử dụng các phương pháp đặc biệt (như ko.observable, trong loại trực tiếp) để làm việc với các mô hình ở góc, a đối tượng js thuần túy sẽ là đủ ".
Felipe Castro

1
Sẽ không có ElementService được đặt tên thích hợp cho mỗi bộ sưu tập dẫn đến một loạt các tệp gần giống nhau?
Collin Allen

29

Tài liệu của Angularjs nêu rõ:

Không giống như nhiều khung công tác khác, Angular không hạn chế hoặc yêu cầu đối với mô hình. Không có lớp nào để kế thừa từ hoặc các phương thức truy cập đặc biệt để truy cập hoặc thay đổi mô hình. Mô hình có thể là nguyên thủy, băm đối tượng hoặc Kiểu đối tượng đầy đủ. Nói tóm lại, mô hình là một đối tượng JavaScript đơn giản.

- Hướng dẫn dành cho nhà phát triển AngularJS - Khái niệm V1.5 - Model

Vì vậy, nó có nghĩa là tùy thuộc vào bạn cách khai báo một mô hình. Đó là một đối tượng Javascript đơn giản.

Cá nhân tôi sẽ không sử dụng Dịch vụ góc vì chúng có nghĩa là hoạt động giống như các đối tượng đơn lẻ mà bạn có thể sử dụng, ví dụ, để giữ các trạng thái toàn cầu trong ứng dụng của bạn.


Bạn nên cung cấp một liên kết đến nơi mà điều này được nêu trong tài liệu. Tôi đã thực hiện một tìm kiếm trên Google cho "Angular không hạn chế hoặc yêu cầu nào đối với mô hình" và nó không xuất hiện ở bất kỳ đâu trong các tài liệu chính thức, theo như tôi có thể nói.

4
đó là trong các tài liệu angularjs cũ (người còn sống trong khi trả lời): github.com/gitsome/docular/blob/master/lib/angular/ngdocs/guide/ít
SC

8

DCI là một mô hình và như vậy không có cách nào để thực hiện điều đó, ngôn ngữ hỗ trợ DCI hoặc không. JS hỗ trợ DCI khá tốt nếu bạn sẵn sàng sử dụng chuyển đổi nguồn và với một số nhược điểm nếu bạn không. Một lần nữa, DCI không liên quan gì đến việc tiêm phụ thuộc hơn là nói một lớp C # có và chắc chắn cũng không phải là một dịch vụ. Vì vậy, cách tốt nhất để thực hiện DCI với angulusJS là thực hiện DCI theo cách JS, khá gần với cách DCI được xây dựng ở vị trí đầu tiên. Trừ khi bạn thực hiện chuyển đổi nguồn, bạn sẽ không thể thực hiện đầy đủ vì các phương thức vai trò sẽ là một phần của đối tượng ngay cả bên ngoài bối cảnh nhưng đó thường là vấn đề với DCI dựa trên phương pháp tiêm. Nếu bạn nhìn vào fullOO.infotrang web có thẩm quyền cho DCI bạn có thể xem qua các triển khai ruby ​​mà họ cũng sử dụng phương pháp tiêm hoặc bạn có thể xem tại đây để biết thêm thông tin về DCI. Chủ yếu là với các ví dụ RUby nhưng công cụ DCI không rõ ràng về điều đó. Một trong những chìa khóa của DCI là những gì hệ thống làm được tách biệt với những gì hệ thống đang có. Vì vậy, đối tượng dữ liệu khá ngu ngốc nhưng một khi đã bị ràng buộc với một vai trò trong một phương thức vai trò bối cảnh thì sẽ có hành vi nhất định. Một vai trò chỉ đơn giản là một định danh, không có gì hơn, khi truy cập một đối tượng thông qua định danh đó thì các phương thức vai trò có sẵn. Không có đối tượng / lớp vai trò. Với phương pháp tiêm, phạm vi của các phương thức vai trò không chính xác như được mô tả nhưng gần đúng. Một ví dụ về bối cảnh trong JS có thể là

function transfer(source,destination){
   source.transfer = function(amount){
        source.withdraw(amount);
        source.log("withdrew " + amount);
        destination.receive(amount);
   };
   destination.receive = function(amount){
      destination.deposit(amount);
      destination.log("deposited " + amount);
   };
   this.transfer = function(amount){
    source.transfer(amount);
   };
}

1
Cảm ơn bạn đã xây dựng các công cụ DCI. Đó là quyển sách đáng đọc. Nhưng câu hỏi của tôi thực sự nhằm mục đích "nơi đặt các đối tượng mô hình trong angularjs". DCI chỉ ở đó để tham khảo, rằng tôi có thể không chỉ có một mô hình, mà còn phân chia nó theo cách DCI. Sẽ chỉnh sửa câu hỏi để làm cho nó rõ ràng hơn.
Nils Blum-Oeste

7

Bài viết này về các mô hình trong AngularJS có thể giúp:

http://joelhooks.com/blog/2013/04/24/modeling-data-and-state-in-your-angularjs-application/


7
Lưu ý rằng các câu trả lời chỉ liên kết không được khuyến khích, các câu trả lời SO phải là điểm cuối của tìm kiếm giải pháp (so với một điểm dừng khác của tài liệu tham khảo, có xu hướng bị cũ theo thời gian). Vui lòng xem xét việc thêm một bản tóm tắt độc lập ở đây, giữ liên kết làm tài liệu tham khảo.
kleopatra

thêm một liên kết như vậy trong một nhận xét về câu hỏi sẽ tốt mặc dù.
jorrebor

Liên kết này thực sự là một bài viết rất hay, nhưng ditto nó sẽ cần được đưa vào một câu trả lời để phù hợp với SO
Jeremy Zerr

5

Như đã nêu bởi các áp phích khác, Angular không cung cấp lớp cơ sở bên ngoài để mô hình hóa, nhưng người ta có thể cung cấp một số chức năng một cách hữu ích:

  1. Phương thức tương tác với API RESTful và tạo đối tượng mới
  2. Thiết lập mối quan hệ giữa các mô hình
  3. Xác thực dữ liệu trước khi lưu vào phần phụ trợ; cũng hữu ích để hiển thị lỗi thời gian thực
  4. Bộ nhớ đệm và tải nhanh để tránh thực hiện các yêu cầu HTTP lãng phí
  5. Móc máy trạng thái (trước / sau khi lưu, cập nhật, tạo, mới, v.v.)

Một thư viện thực hiện tốt tất cả những điều này là ngActiveResource ( https://github.com/FacemonyCreative/ngActiveResource ). Tiết lộ đầy đủ - Tôi đã viết thư viện này - và tôi đã sử dụng nó thành công trong việc xây dựng một số ứng dụng quy mô doanh nghiệp. Nó đã được thử nghiệm tốt và cung cấp một API quen thuộc với các nhà phát triển Rails.

Nhóm của tôi và tôi tiếp tục tích cực phát triển thư viện này và tôi rất muốn thấy nhiều nhà phát triển Angular đóng góp cho nó và chiến đấu thử nghiệm nó.


Chào! Điều này thực sự tuyệt vời! Tôi sẽ cắm nó vào ứng dụng của tôi ngay bây giờ. Thử nghiệm trận chiến chỉ mới bắt đầu.
J. Bruni

1
Tôi chỉ nhìn vào bài viết của bạn và tự hỏi sự khác biệt giữa dịch vụ của bạn ngActiveResourcevà Angular là gì $resource. Tôi là một người mới đối với Angular và nhanh chóng duyệt qua cả hai bộ tài liệu, nhưng chúng dường như cung cấp rất nhiều sự chồng chéo. Được ngActiveResourcephát triển trước khi $resourcedịch vụ có sẵn?
Eric B.

5

Một câu hỏi cũ hơn, nhưng tôi nghĩ chủ đề này có liên quan hơn bao giờ hết được đưa ra hướng đi mới của Angular 2.0. Tôi muốn nói rằng một cách thực hành tốt nhất là viết mã với càng ít phụ thuộc vào một khung cụ thể càng tốt. Chỉ sử dụng các phần cụ thể của khung mà nó thêm giá trị trực tiếp.

Hiện tại có vẻ như dịch vụ Angular là một trong số ít các khái niệm sẽ đưa nó đến thế hệ tiếp theo của Angular, vì vậy có lẽ nên thông minh theo hướng dẫn chung là chuyển tất cả logic sang dịch vụ. Tuy nhiên, tôi sẽ lập luận rằng bạn có thể tạo các mô hình tách rời ngay cả khi không phụ thuộc trực tiếp vào các dịch vụ Angular. Tạo các đối tượng khép kín chỉ có phụ thuộc và trách nhiệm cần thiết có lẽ là cách để đi. Nó cũng làm cho cuộc sống dễ dàng hơn rất nhiều khi làm kiểm tra tự động. Trách nhiệm duy nhất là một công việc ồn ào những ngày này, nhưng nó có rất nhiều ý nghĩa!

Dưới đây là một ví dụ về một patter tôi cho là tốt để tách mô hình đối tượng khỏi dom.

http://www.syntaxsuccess.com/viewarticle/548ebac8ecdac75c8a09d58e

Mục tiêu chính là cấu trúc mã của bạn theo cách làm cho nó dễ sử dụng từ một đơn vị kiểm tra như từ chế độ xem. Nếu bạn đạt được rằng bạn có vị trí tốt để viết các bài kiểm tra thực tế và hữu ích.


4

Tôi đã cố gắng giải quyết vấn đề chính xác trong bài viết trên blog này .

Về cơ bản, ngôi nhà tốt nhất cho mô hình dữ liệu là trong các dịch vụ và nhà máy. Tuy nhiên, tùy thuộc vào cách bạn truy xuất dữ liệu của mình và mức độ phức tạp của các hành vi bạn cần, có rất nhiều cách khác nhau để thực hiện. Angular hiện không có cách tiêu chuẩn hoặc thực hành tốt nhất.

Bài đăng bao gồm ba cách tiếp cận, sử dụng $ http , $ resourceRestangular .

Dưới đây là một số mã ví dụ cho từng loại, với một getResult()phương thức tùy chỉnh trên mô hình Công việc:

Restangular (peasy dễ dàng):

angular.module('job.models', [])
  .service('Job', ['Restangular', function(Restangular) {
    var Job = Restangular.service('jobs');

    Restangular.extendModel('jobs', function(model) {
      model.getResult = function() {
        if (this.status == 'complete') {
          if (this.passed === null) return "Finished";
          else if (this.passed === true) return "Pass";
          else if (this.passed === false) return "Fail";
        }
        else return "Running";
      };

      return model;
    });

    return Job;
  }]);

$ resource (hơi phức tạp hơn một chút):

angular.module('job.models', [])
    .factory('Job', ['$resource', function($resource) {
        var Job = $resource('/api/jobs/:jobId', { full: 'true', jobId: '@id' }, {
            query: {
                method: 'GET',
                isArray: false,
                transformResponse: function(data, header) {
                    var wrapped = angular.fromJson(data);
                    angular.forEach(wrapped.items, function(item, idx) {
                        wrapped.items[idx] = new Job(item);
                    });
                    return wrapped;
                }
            }
        });

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    }]);

$ http (lõi cứng):

angular.module('job.models', [])
    .service('JobManager', ['$http', 'Job', function($http, Job) {
        return {
            getAll: function(limit) {
                var params = {"limit": limit, "full": 'true'};
                return $http.get('/api/jobs', {params: params})
                  .then(function(response) {
                    var data = response.data;
                    var jobs = [];
                    for (var i = 0; i < data.objects.length; i ++) {
                        jobs.push(new Job(data.objects[i]));
                    }
                    return jobs;
                });
            }
        };
    }])
    .factory('Job', function() {
        function Job(data) {
            for (attr in data) {
                if (data.hasOwnProperty(attr))
                    this[attr] = data[attr];
            }
        }

        Job.prototype.getResult = function() {
            if (this.status == 'complete') {
                if (this.passed === null) return "Finished";
                else if (this.passed === true) return "Pass";
                else if (this.passed === false) return "Fail";
            }
            else return "Running";
        };

        return Job;
    });

Bản thân bài đăng trên blog đi sâu vào chi tiết hơn về lý do tại sao bạn có thể sử dụng từng phương pháp, cũng như các ví dụ mã về cách sử dụng các mô hình trong bộ điều khiển của bạn:

Mô hình dữ liệu AngularJS: $ http VS $ resource VS Restangular

Có khả năng Angular 2.0 sẽ cung cấp một giải pháp mạnh mẽ hơn cho mô hình hóa dữ liệu giúp mọi người trên cùng một trang.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.