Dove mettere i dati e il comportamento del modello? [Tl; dr; Usa servizi]


341

Sto lavorando con AngularJS per il mio ultimo progetto. Nella documentazione e nelle esercitazioni tutti i dati del modello vengono inseriti nell'ambito del controller. Capisco che deve essere lì per essere disponibile per il controller e quindi all'interno delle viste corrispondenti.

Tuttavia, non credo che il modello debba effettivamente essere implementato lì. Potrebbe essere complesso e avere attributi privati ​​per esempio. Inoltre, potresti voler riutilizzarlo in un altro contesto / app. Mettere tutto nel controller interrompe totalmente il modello MVC.

Lo stesso vale per il comportamento di qualsiasi modello. Se dovessi usare l' architettura DCI e separare il comportamento dal modello di dati, dovrei introdurre oggetti aggiuntivi per contenere il comportamento. Ciò verrebbe fatto introducendo ruoli e contesti.

DCI == D ata C ollaboration I nteraction

Naturalmente i dati e il comportamento del modello potrebbero essere implementati con semplici oggetti javascript o qualsiasi modello di "classe". Ma quale sarebbe il modo AngularJS per farlo? Usando i servizi?

Quindi si arriva a questa domanda:

Come si implementano i modelli disaccoppiati dal controller, seguendo le best practice di AngularJS?


12
Voterei questa domanda se potessi definire DCI o almeno fornire il modulo spiegato. Non ho mai visto questo acronimo in nessuna letteratura software. Grazie.
Jim Raden,

13
Ho appena aggiunto un link per DCI come riferimento.
Nils Blum-Oeste,

1
@JimRaden DCI è Dataq, contesto, interazione ed è un paradigma formulato in primo luogo dal padre di MVC (Trygve Reenskauge). Ormai c'è un bel po 'di letteratura sull'argomento. Una buona lettura è Coplien e Bjørnvig "Lean architecture"
Rune FS

3
Grazie. Nel bene e nel male, ormai la maggior parte delle persone non conosce nemmeno la letteratura originale. Ci sono 55 milioni di articoli su MVC, secondo Google, ma solo 250.000 citano MCI e MVC. E su Microsoft.com? 7. AngularJS.org non menziona nemmeno l'acronimo DCI: "La tua ricerca - sito: angularjs.org dci - non ha prodotto risultati per alcun documento".
Jim Raden,

Gli oggetti risorse sono fondamentalmente i modelli in Angular.js ... li sto estendendo.
Salman von Abbas,

Risposte:


155

È necessario utilizzare i servizi se si desidera qualcosa utilizzabile da più controller. Ecco un semplice esempio inventato:

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
Quale sarebbe il vantaggio di utilizzare un servizio semplicemente creando un semplice oggetto Javascript come modello e assegnandolo allo scopo del controller?
Nils Blum-Oeste,

22
Nel caso in cui sia necessaria la stessa logica condivisa tra più controller. Inoltre, in questo modo è più facile testare le cose in modo indipendente.
Andrew Joslin,

1
L'ultimo esempio di tipo succhiato, questo ha più senso. L'ho modificato.
Andrew Joslin,

9
Sì, con un semplice vecchio oggetto Javascript non saresti in grado di iniettare nulla di angolare nel tuo ListService. Come in questo esempio, se è necessario $ http.get per recuperare i dati dell'elenco all'inizio o se è necessario iniettare $ rootScope in modo da poter $ trasmettere eventi.
Andrew Joslin,

1
Per rendere questo esempio più DCI simile, i dati non dovrebbero essere al di fuori di ListService?
PiTheNumber

81

Attualmente sto provando questo schema, che, sebbene non DCI, fornisce un classico disaccoppiamento servizio / modello (con servizi per parlare con i servizi web (aka modello CRUD) e modello che definisce le proprietà e i metodi dell'oggetto).

Si noti che utilizzo questo modello solo quando l'oggetto modello necessita di metodi che funzionano sulle proprie proprietà, che probabilmente userò ovunque (come getter / setter migliorati). Sto Non sostenendo facendo questo per ogni servizio in modo sistematico.

EDIT: Pensavo che questo modello andasse contro il mantra "Il modello angolare è semplicemente un vecchio oggetto javascript", ma ora mi sembra che questo modello sia perfettamente perfetto.

EDIT (2): Per essere ancora più chiaro, uso una classe Model solo per fattorizzare getter / setter semplici (ad esempio: da usare nei modelli di vista). Per le grandi logiche aziendali, consiglio di utilizzare servizi separati che "conoscono" il modello, ma che siano tenuti separati da essi e includano solo la logica aziendale. Chiamalo livello di servizio "esperto di affari" se lo desideri

service / ElementServices.js (nota come viene iniettato Element nella dichiarazione)

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 (utilizzando angularjs Factory, creato per la creazione di oggetti)

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
Sto solo entrando in Angular, ma sarei curioso di sapere se / perché i veterani penserebbero che questa sia un'eresia. Questo è probabilmente il modo in cui inizialmente mi avvicinerei anch'io. Qualcuno potrebbe fornire un feedback?
Aaronius,

2
@Aaronius solo per essere chiari: non ho mai letto "non dovresti mai farlo" su nessun documento o blog di angularjs, ma ho sempre letto cose come "angularjs non ha bisogno di un modello, sta semplicemente usando javascript vecchio" e ho dovuto scoprire questo schema da solo. Dato che questo è il mio primo vero progetto su AngularJS, sto mettendo quegli avvertimenti forti, in modo che le persone non copino / incollino senza pensare prima.
Ben G,

Ho optato per uno schema approssimativamente simile. È un peccato che Angular non abbia alcun supporto reale (o apparentemente voglia di supportare) un modello nel senso "classico".
drt,

3
Non mi sembra un'eresia, stai usando fabbriche per quello per cui sono state create: costruire oggetti. Credo che la frase "angularjs non abbia bisogno di un modello" significhi "non è necessario ereditare da una classe speciale o utilizzare metodi speciali (come ko.observable, in knockout) per lavorare con i modelli in angular, a l'oggetto js puro sarà sufficiente ".
Felipe Castro,

1
Non avere un ElementService appropriatamente chiamato per ogni raccolta comporterebbe un mucchio di file quasi identici?
Collin Allen

29

La documentazione di Angularjs afferma chiaramente:

A differenza di molti altri framework, Angular non applica restrizioni o requisiti al modello. Non ci sono classi da ereditare o metodi di accesso speciali per accedere o modificare il modello. Il modello può essere primitivo, hash di oggetto o un tipo di oggetto completo. In breve, il modello è un semplice oggetto JavaScript.

- Guida per gli sviluppatori di AngularJS - Concetti V1.5 - Modello

Quindi significa che dipende da te come dichiarare un modello. È un semplice oggetto Javascript.

Personalmente non userò i servizi angolari poiché erano pensati per comportarsi come oggetti singleton che è possibile utilizzare, ad esempio, per mantenere gli stati globali in tutta l'applicazione.


È necessario fornire un collegamento a dove è indicato nella documentazione. Ho fatto una ricerca su Google per "Angular non ha restrizioni o requisiti sul modello" , e non compare da nessuna parte nei documenti ufficiali, per quanto ne so.

4
era nei vecchi documenti di angularjs (quello vivo mentre rispondeva): github.com/gitsome/docular/blob/master/lib/angular/ngdocs/guide/…
SC

8

DCI è un paradigma e come tale non esiste un modo angularJS per farlo, o il linguaggio supporta DCI o no. JS supporta DCI piuttosto bene se sei disposto a utilizzare la trasformazione dei sorgenti e con alcuni svantaggi se non lo sei. Ancora una volta DCI non ha più a che fare con l'iniezione di dipendenza di quanto non abbia una classe C # e sicuramente non è nemmeno un servizio. Quindi il modo migliore per fare DCI con angulusJS è fare DCI nel modo JS, che è abbastanza vicino a come DCI è formulato in primo luogo. A meno che non si esegua la trasformazione del codice sorgente, non sarà possibile farlo completamente poiché i metodi di ruolo faranno parte dell'oggetto anche al di fuori del contesto, ma questo è generalmente il problema con il DCI basato sull'iniezione di metodo. Se guardi fullOO.infoil sito autorevole per DCI potresti dare un'occhiata alle implementazioni ruby ​​che usano anche il metodo di iniezione o potresti dare un'occhiata qui per maggiori informazioni su DCI. È principalmente con esempi RUby, ma la roba DCI è agnostica a questo. Una delle chiavi di DCI è che ciò che fa il sistema è separato da ciò che è il sistema. Quindi gli oggetti dati sono piuttosto stupidi ma una volta associati a un ruolo in un contesto i metodi del ruolo rendono disponibili determinati comportamenti. Un ruolo è semplicemente un identificatore, niente di più, quando si accede a un oggetto tramite quell'identificatore, sono disponibili i metodi di ruolo. Non esiste alcun oggetto / classe di ruolo. Con l'iniezione del metodo, l'ambito dei metodi di ruolo non è esattamente come descritto ma vicino. Un esempio di un contesto in JS potrebbe essere

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
Grazie per aver elaborato le cose DCI. È un'ottima lettura. Ma le mie domande mirano davvero a "dove mettere gli oggetti del modello in angularjs". DCI è solo lì per riferimento, che potrei non solo avere un modello, ma dividerlo nel modo DCI. Modificherà la domanda per renderla più chiara.
Nils Blum-Oeste,

7

Questo articolo sui modelli in AngularJS potrebbe aiutare:

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


7
Si noti che le risposte solo al collegamento sono scoraggiate, le risposte SO dovrebbero essere il punto finale di una ricerca di una soluzione (rispetto a un altro scalo di riferimento, che tende a diventare stantio nel tempo). Si prega di considerare l'aggiunta di una sinossi stand-alone qui, mantenendo il collegamento come riferimento.
Kleopatra,

l'aggiunta di un tale collegamento in un commento sulla domanda andrebbe bene però.
Jorrebor,

Questo link è in realtà un ottimo articolo, ma idem dovrebbe essere trasformato in una risposta per essere appropriato per SO
Jeremy Zerr,

5

Come affermato da altri poster, Angular non offre una classe di base pronta per la modellazione, ma si possono utilmente fornire diverse funzioni:

  1. Metodi per l'interazione con un'API RESTful e la creazione di nuovi oggetti
  2. Stabilire relazioni tra modelli
  3. Convalida dei dati prima di persistere nel backend; utile anche per visualizzare errori in tempo reale
  4. Memorizzazione nella cache e caricamento lento per evitare di sprecare richieste HTTP
  5. State hook della macchina (prima / dopo il salvataggio, l'aggiornamento, la creazione, il nuovo, ecc.)

Una libreria che fa bene tutte queste cose è ngActiveResource ( https://github.com/FacultyCreative/ngActiveResource ). Divulgazione completa - ho scritto questa libreria - e l'ho utilizzata con successo nella creazione di numerose applicazioni su scala aziendale. È ben testato e fornisce un'API che dovrebbe essere familiare agli sviluppatori di Rails.

Io e il mio team continuiamo a sviluppare attivamente questa libreria e mi piacerebbe vedere altri sviluppatori angolari contribuire ad essa e testare la battaglia.


Hey! Questo è davvero fantastico! Lo collegherò alla mia app in questo momento. I test di battaglia sono appena iniziati.
J. Bruni

1
Sto solo guardando il tuo post e mi chiedevo quali siano le differenze tra il tuo ngActiveResourcee il $resourceservizio di Angular . Sono un po 'nuovo di Angular e ho rapidamente consultato entrambi i set di documenti, ma sembrano offrire molte sovrapposizioni. È stato ngActiveResourcesviluppato prima che il $resourceservizio fosse disponibile?
Eric B.

5

Una domanda più vecchia, ma penso che l'argomento sia più pertinente che mai data la nuova direzione di Angular 2.0. Direi che una buona pratica è scrivere codice con il minor numero possibile di dipendenze su un determinato framework. Utilizzare le parti specifiche del framework solo dove aggiunge valore diretto.

Attualmente sembra che il servizio angolare sia uno dei pochi concetti che lo porterà alla prossima generazione di angolari, quindi è probabilmente intelligente seguire le linee guida generali per spostare tutta la logica nei servizi. Tuttavia, direi che puoi creare modelli disaccoppiati anche senza una dipendenza diretta dai servizi angolari. Creare oggetti autonomi con solo dipendenze e responsabilità necessarie è probabilmente la strada da percorrere. Inoltre semplifica la vita quando si eseguono test automatici. La singola responsabilità è un lavoro da brivido in questi giorni, ma ha molto senso!

Ecco un esempio di scalpiccio che considero buono per il disaccoppiamento del modello a oggetti dal dom.

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

Un obiettivo chiave è strutturare il codice in modo da renderlo facile da usare sia da un test unitario che da una vista. Se ti rendi conto che sei ben posizionato per scrivere test realistici e utili.


4

Ho cercato di affrontare l'esatto problema in questo post del blog .

Fondamentalmente, la casa migliore per la modellazione dei dati è nei servizi e nelle fabbriche. Tuttavia, a seconda di come recuperare i dati e della complessità dei comportamenti necessari, esistono diversi modi per procedere all'implementazione. Attualmente Angular non ha metodi standard o best practice.

Il post tratta tre approcci, usando $ http , $ resource e Restangular .

Ecco un codice di esempio per ciascuno di essi, con un getResult()metodo personalizzato sul modello Job:

Restangular (facile peasy):

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 (leggermente più contorto):

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 (hardcore):

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;
    });

Il post sul blog stesso fornisce ulteriori dettagli sul ragionamento alla base del motivo per cui è possibile utilizzare ciascun approccio, nonché esempi di codice su come utilizzare i modelli nei controller:

Modelli di dati AngularJS: $ http VS $ risorsa VS Restangular

C'è una possibilità che Angular 2.0 offra una soluzione più solida alla modellazione dei dati che porti tutti sulla stessa pagina.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.