How to deal with data modeling in ANGULARJS framework _angularjs

Source: Internet
Author: User
Tags uppercase letter

We know that ANGULARJS does not have the data modeling scheme that is available with erecting. Rather, in a rather abstract way, let's use JSON data as a model in controller. But as time went on and the project grew, I realized that this modeling approach no longer meets the needs of our projects. In this article I will describe how to work with data modeling in my ANGULARJS application.

Defining a model for controller

Let's start with a simple example. I want to display a book page. Here is the controller (Controller):

Bookcontroller

App.controller (' Bookcontroller ', [' $scope ', function ($scope) {
  $scope. Book = {
    id:1,
    name: ' Harry Potter ' ,
    Author: ' J. K. Rowling ',
    stores: [
      {id:1, Name: ' Barnes & Noble ', quantity:3},
      {id:2, name: ' Wa Terstones ', quantity:2},
      {id:3, Name: ' Book Depository ', Quantity:5}
    ]
  };

This controller creates a book model that we can use in a later template (Templage).

Template for displaying a book

<div ng-controller= "Bookcontroller" >
  Id: <span ng-bind= "book.id" ></span>
   
  name:<input Type= "text" ng-model= "Book.name"/>
   
  Author: <input type= "text" ng-model= "Book.author"/>
>

If we need to get the book data from the backend API, we need to use $http:
Bookcontroller with $http

App.controller (' Bookcontroller ', [' $scope ', ' $http ', function ($scope, $http) {
  var bookid = 1;
 
  $http. Get (' ourserver/books/' + bookid). Success (function (bookdata) {
    $scope. book = BookData;
  };
}]);

Note that the bookdata here is still a JSON object. Next we want to use this data to do something. For example, update book information, delete books, and even some other not related to the background of the operation, such as the size of the requested picture to generate a book picture URL, or to determine whether the book is valid. These methods can be defined in the controller.

Bookcontroller with several book actions

App.controller (' Bookcontroller ', [' $scope ', ' $http ', function ($scope, $http) {
  var bookid = 1;
 
  $http. Get (' ourserver/books/' + bookid). Success (function (bookdata) {
    $scope. book = BookData;
  });
 
  $scope. Deletebook = function () {
    $http. Delete (' ourserver/books/' + bookid);
 
  $scope. Updatebook = function () {
    $http. Put (' ourserver/books/' + BookID, $scope. book);
 
  $scope. Getbookimageurl = function (width, height) {return
    ' our/image/service/' + bookid + '/width/height ';
  };
 
  $scope. isavailable = function () {
    if (! $scope. book.stores | | $scope. book.stores.length = = 0) {return
      false;
    }
    Return $scope. Book.stores.some (function (store) {return
      store.quantity > 0;}
    );

And then in our template:

Template for displaying a complete book

<div ng-controller= "Bookcontroller" >
  <div ng-style= "{backgroundimage: ' url (' + getbookimageurl" (100, 100 + ') '} ' ></div>
  Id: <span ng-bind= "book.id" ></span>
   
  name:<input type= "text" Ng-model= "Book.name"/>
   
  Author: <input type= "text" ng-model= "Book.author"/> is
   
  Available: <span Ng-bind= "IsAvailable" ()? ' Yes ': ' No ' ' ></span>
   
  <button ng-click= "Deletebook ()" >Delete</button>
   
  <button Ng-click= "Updatebook ()" >Update</button>
</div>

Sharing model between controllers
if the structure and methods of a book are only related to a single controller, then our work is now manageable. But as applications grow, other controllers also need to deal with books. Those controllers often need to get a book, update it, delete it, or get its picture URL and see if it works. Therefore, we need to share the behavior of these books among the controllers. We need to use a factory to return the book behavior to achieve this. Before I start writing a factory, I'd like to mention here that we create a factory to return objects with these book helper methods, but I prefer to use prototype to construct a book class, which I think is a more correct choice:

Book Model Service

App.factory (' book ', [' $http ', function ($http) {function Book (bookdata) {if (bookdata) {This.setdata (bookda
  TA):}//Some other initializations related to book};
    Book.prototype = {Setdata:function (bookdata) {angular.extend (this, bookdata);
      }, Load:function (id) {var scope = this;
      $http. Get (' ourserver/books/' + bookid). Success (function (BookData) {scope.setdata (bookdata);
    });
    }, Delete:function () {$http. Delete (' ourserver/books/' + bookid);
    Update:function () {$http. put (' ourserver/books/' + BookID, this);
    }, Getimageurl:function (width, height) {return ' our/image/service/' + this.book.id + '/width/height ';
      }, Isavailable:function () {if (!this.book.stores | | this.book.stores.length = = 0) {return false;
      return This.book.stores.some (the function (store) {return store.quantity > 0;
    });
  }
  };
return book; }]);
 

In this way, all the behavior related to books is encapsulated in the book Service. Now, we use this bright-eyed book service in the Bookcontroller.

Bookcontroller that uses book model

App.controller (' Bookcontroller ', [' $scope ', ' book ', Function ($scope, book) {
  $scope. Book = new book ();
  $scope. Book.load (1);
}]);

As you can see, the controller becomes very simple. It creates a book instance, assigns it to scope, and loads it from the background. When a book is loaded successfully, its properties are changed and the template is updated. Keep in mind that other controllers want to use book functionality, simply by injecting the books service. In addition, we have to change the way template uses book.

Template that uses book instance

<div ng-controller= "Bookcontroller" >
  <div ng-style= "{backgroundimage: ' url (' + book.getimageurl" (100, ) + ') ' ' ></div>
  Id: <span ng-bind= "book.id" ></span>
   
  name:<input type= "text" Ng-model= "Book.name"/>
   
  Author: <input type= "text" ng-model= "Book.author"/> is
   
  Available: <span Ng-bind= "Book.isavailable" ()? ' Yes ': ' No ' ' ></span>
   
  <button ng-click= "Book.delete ()" >Delete</button>
   
  <button Ng-click= "Book.update ()" >Update</button>
</div>

Here, we know how to model a data, encapsulate his methods in a class, and share it across multiple controllers without having to write duplicate code.
Use the same book model in multiple controllers

We defined a book model and used it in multiple controllers. After using this modeling architecture you will notice that there is a serious problem. So far, we've assumed that multiple controllers operate on books, but what if there are two controllers dealing with the same book at the same time?

Suppose we have a section of our page with the name of all our books, and another area can update a book. For these two areas, we have two different controllers. The first load book list, the second loads a particular book. Our user modifies the name of the book in the second area and clicks on the "Update" button. After the update operation succeeds, the name of the book will be changed. But in the book list, this user always sees the name before the change! The real situation is that we created two different book instances for the same book-one for use in the book list and the other for books. When a user modifies a book name, it actually modifies only the attributes in the latter instance. However, the book examples in the book list have not been changed.

The solution to this problem is to use the same book instance in all the controllers. In this way, both the book list and the pages and the controller of the book have the same book instance, and once the instance changes, it is immediately reflected in all the views. So in this way, we need to create a Booksmanager service (we don't have the uppercase letter B, because this is an object rather than a class) to manage all the book instance pools, and to return to these book instances. If the requested book instance is not in the instance pool, the service creates it. If it is already in the pool, return it directly. Keep in mind that all methods of loading a book will eventually be defined in the Booksmanager service because it is the only component that provides a book instance.

Booksmanager Service

App.factory (' Booksmanager ', [' $http ', ' $q ', ' book ', Function ($http, $q, book) {var Booksmanager = {_pool: {},
 
      _retrieveinstance:function (BookID, bookdata) {var instance = This._pool[bookid];
      if (instance) {Instance.setdata (bookdata);
        else {instance = new book (bookdata);
      This._pool[bookid] = instance;
    return instance;
    }, _search:function (BookID) {return this._pool[bookid];
 
      }, _load:function (BookID, deferred) {var scope = this; $http. Get (' ourserver/books/' + bookid). Success (the function (bookdata) {var book = Scope._retrieveinstance (
          Bookdata.id, BookData);
        Deferred.resolve (book);
        }). Error (function () {deferred.reject ();
    }); (BookID)
      {var deferred = $q. Defer (); var book = this. _search (BookID);
      if (book) {deferred.resolve (book);
      else {this._load (bookid, deferred);
    return deferred.promise; },/* Use the ' this ' function in ' order ' to ' get instances ' all ' books */loadallbooks:function () {var deferr
      ed = $q. Defer ();
      var scope = this;
          $http. Get (' Ourserver/books). Success (function (Booksarray) {var books = [];
            Booksarray.foreach (function (bookdata) {var book = Scope._retrieveinstance (Bookdata.id, bookdata);
          Books.push (book);
 
          });
        Deferred.resolve (books);
        }). Error (function () {deferred.reject ();
      });
    return deferred.promise; },/* This function are useful when we got somehow the book data and we wish to store it or update the pool and get a
      Book Instance in return */Setbook:function (bookdata) {var scope = this;
     var book = This._search (bookdata.id); if (book) {book.setdata (bookdata);
      else {book = scope._retrieveinstance (BookData);
    return book;
  },
 
  };
return booksmanager;
 }]);

Here are the code for our Editablebookcontroller and Bookslistcontroller two controllers:

Editablebookcontroller and Bookslistcontroller that uses Booksmanager

App.factory (' book ', [' $http ', function ($http) {
  function book (bookdata) {
    if (bookdata) {
      This.setdata ( bookdata):
    }
    
//Some other initializations related to book
  };
  Book.prototype = {
    setdata:function (bookdata) {
      angular.extend (this, bookdata);
    },
    Delete: function () {
      $http. Delete (' ourserver/books/' + BookID);
    },
    update:function () {
      $http. Put (' ourserver/books/' + BookID, this];
    },
    getimageurl:function (width, height) {return
      ' Our/image/service /' + this.book.id + '/width/height ';
    },
    isavailable:function () {
      if (!this.book.stores | | This.book.stores.length = = 0) {return
        false;
      }
      return This.book.stores.some (the function (store) {return
        store.quantity > 0;
      });}
  ;
  return book;
}];

It should be noted that the module (template) still retains the original way of using the book instance. Now the application holds only one instance of book ID 1, and all changes that occur will be reflected on each page that uses it.

Some pits in the Angularjs
Flashing of the UI

Angular's automatic data binding is a bright spot, however, the flip side of this is that before angular initialization, the page might show the user an unresolved expression. When the DOM is ready, angular calculates and replaces the corresponding value. This can lead to an ugly blinking effect.
The above scenario is to render the sample code in the angular tutorial:

<body ng-controller= "Phonelistctrl" >
 <ul>
  <li ng-repeat= "phone in Phones" >
   {{ Phone.name}}
   <p>{{phone.snippet}}</p>
  </li>
 </ul>
</body>

If you're doing a spa (single page application), this problem will only appear when the page is first loaded, and fortunately, it's easy to put an end to this: Discard the {}} expression and use the Ng-bind directive instead

<body ng-controller= "Phonelistctrl" >
 <ul>
  <li ng-repeat= "phone in Phones" >
   < Span ng-bind= "Phone.name" ></span>
   <p ng-bind= "Phone.snippet" >optional:visually pleasing placeholder</p>
  </li>
 </ul>
</body>
 

You need a tag to include this instruction, so I added a <span> to phone name.

So what happens when you initialize, and the value in this tag is displayed (but you can choose to set a null value). Then, when angular initializes and replaces the tag intrinsic value with the expression result, note that you do not need to add braces inside the ng-bind. It's more concise! If you need to conform to an expression, use ng-bind-template,

If you use this instruction, in order to differentiate between string literals and expressions, you need to use curly braces

Another approach is to completely hide the elements and even hide the entire application until the angular is ready.

Angular also provides a Ng-cloak directive that works by inject CSS rules in the initialization phase, or you can include this CSS hidden rule to your own stylesheet. When the angular is ready, the cloak style is removed, allowing our application (or elements) to be rendered immediately.

Angular does not rely on jquery. In fact, the angular source contains an embedded lightweight jquery:jqlite. When angular detects jquery appearing on your page, he uses the jquery instead of Jqlite, and the direct evidence is the element abstraction layer in angular. For example, access the element you want to apply to in directive.

Angular.module (' jqdependency ', [])
 . Directive (' Failswithoutjquery ', function () {return
  {
   restrict: ' A ' ,
   link:function (scope, element, attrs) {
        element.hide (4000)}}
);

But is this element a jqlite or a jquery element? Depending on what is written in the manual:

All element references in the angular are packaged by jquery or jqlite; they are never pure DOM references

So angular if jquery is not detected, then the Jqlite element is used, the Hide () method value can be used for the jquery element, so the sample code can only be used when jquery is detected. If you (accidentally) modify the order in which Angularjs and jquery appear, this code will fail! Although it's okay to move the sequence of the scripts, it doesn't happen very often, but it really bothers me when I start to modularize the code. Especially when you start using the module loader (like Requirejs), my solution is to display the statement in the configuration angular really relies on jquery

Another approach is that you do not invoke jquery-specific methods by angular element wrappers, but instead use $ (element). Hide (4000) to indicate your intention. This depends, even if the script loading order is modified.

Compression

Of particular note is the angular application compression problem. Otherwise the error message such as ' Unknown provider:aprovider <-a ' will leave you scratching your head. Like many other things, this error is not found in official documents. In short, angular relies on parameter names for dependency injection. The compressor is not aware of this. This is different from the normal parameter names in angular, as it is their duty to shorten the script as much as possible. Supposed Use the "friendly compression method" to inject the method. Look here:

Module.service (' MyService ', function ($http, $q) {
//This breaks when minified
});
To this:

module.service (' MyService ', [' $http ', ' $q ', function ($http, $q) {
//Using the-array syntax to declare D Ependencies works with Minification<b>!</b>
}]);

This array syntax is a good solution to this problem. My advice is to write this way from now on, and if you decide to compress JavaScript, this approach will allow you to take a lot less detours. It seems to be a automatic rewriter mechanism, and I'm not quite sure how it works.

One final tip: If you want to use array syntax to duplicate your functions, apply it in all angular dependency injection places. including directives, and the controllers in Directive. Don't forget the comma (from experience)

The directive itself needs array injection syntax:
module.directive (' Directive-with-controller ', [' MyService ', function (MyService) {return
  {
   controller: [' $timeout ', function ($timeout) {
    
//But this controller needs Array injection syntax, Too! 
   }],
   link:function (scope, element, Attrs, ctrl) {
 
   }
  }
);

Note: The link function does not require array syntax because he does not have a real injection. This is the function that is called directly by angular. Directive-level dependency injection is also used in link function.

directive will never ' finish '

In directive, a hair-losing thing is that directive has ' done ' but you never know. This notification is especially important when incorporating jquery plug-ins into directive. Suppose you want to use Ng-repeat to display Dynamic Data as a jquery DataTable. When all the data is loaded on the page, you only need to call $ ('. mytable). DataTable (). But my concubine can't do it!

Why, then? Angular data binding is implemented through a continuous digest loop. Based on this, there is no time for ' rest ' in the angular framework. One solution is to place the call of the jquery DataTable outside the current digest loop, which can be done using the timeout method.

Angular.module (' table ', []). directive (' mytable ', [' $timeout ', function ($timeout) {return
  {
   restrict: ' E ',
   Template: ' <table class= ' mytable ' > ' +
          ' <thead><tr><th>counting</th></tr ></thead> ' +
          ' <tr ng-repeat= ' data in datas ' ><td></td></tr> ' +
        ' </table > ',
   link:function (scope, element, Attrs, ctrl) {
     Scope.datas = ["One", "two", "three"]
     
//doesn ' t work, s Hows an empty table:
     
//$ ('. MyTable ', Element). DataTable () 
     
//But This does:
     $timeout (function () {
      $ ('. MyTable ', Element). dataTable ();
     }, 0
   }}
  }
]);

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.