This lesson is for PRO members.

Unlock this lesson NOW!
Already subscribed? sign in

Add Caching to the Model Base Class

6:58 Angular 1.x lesson by

In the previous lessons we created a base class and looked at a caching mechanism for our models. In this lesson we will expand on that concept by test driving the addition of caching to our model base class, as well as some initial core functionality. This is advanced subject matter, and will require study of the code as well as watching the video.

Get the Code Now
click to level up

egghead.io comment guidelines

Avatar
egghead.io

In the previous lessons we created a base class and looked at a caching mechanism for our models. In this lesson we will expand on that concept by test driving the addition of caching to our model base class, as well as some initial core functionality. This is advanced subject matter, and will require study of the code as well as watching the video.

Avatar
Loy

Very good post but was wondering if we can take advantage of caching in resource object like

return {
Things: $resource('api/person/:id', {}, {
GetPerson : {
method : 'GET',
cache : true
}
})
};

And also why is cache factory not used for caching?

In reply to egghead.io
Avatar
Joel

You can definitely use $resource as you describe, but this is building an alternative to $resource from scratch. It will serve a similar purpose, as such will share features.

To that same end, cache factory is not used.

In reply to Loy
Avatar
Brett Shollenberger

Ditto what Joel said. While we could use $resource and $cacheFactory we're taking a theoretical look at caching in Javascript, why it's important, and how we can replicate the functionality of Angular (to some degree). I like the idea of understanding how your tools works so you can use them with confidence :)

In reply to Loy
Avatar
Thomas

Hi Brett

I tried to build my own resource manager around $resource and $cacheFactory, but have decided to give your ActiveResource a try instead, and so far it is looking very great.

However, I have a little problem as I'm working with an api, that structures its levels of associations so the endpoint of comments related to posts is like this:

/posts/:id/comments

so when Post.find(1) tries to fetch /comments?post_id=1 -- because posts hasMany('comments') -- it returns nothing.

A can't figure out, if there is some way to configure ActiveResource to work with this api design, do you have any suggestions?

In reply to Brett Shollenberger
Avatar
Wayne

In the previous lessons we created a base class and looked at a caching mechanism for our models. In this lesson we will expand on that concept by test driving the addition of caching to our model base class, as well as some initial core functionality. This is advanced subject matter, and will require study of the code as well as watching the video.

This is really advanced! ...and I like it!

Also, I really enjoy vi/vim myself, but have never seen anyone use it quite this way...I'm so jealous!!! :)

In reply to egghead.io
Avatar
Magnus

I've really enjoyed this series. One thing regarding the use of models that I haven't seen talked much about is how to validate that the model I send to the server and the server sends back to the UI is what the UI expects.

If I send the object
User
{
contactid: "123",
foo: "abc"
};

I do a post to the server to update the User object
$http.post('http://localhost/contact/update', JSON.stringify(user)).success(function( data, status) {

}

If the server sends me back the User object

User
{
contactid: "123",
bar "abc"
};

How would I be able to detect that in a nice way? Seems like there should be a nice way of handling it without having to just compare the object I sent and the object I recieved and having to know what data I may expect to be changed.

In reply to Wayne
Avatar
Kevin

I'm having a hard time following the definition of the privateVariable, particularly how it's setting up accessors.

The use of val variable throughout the function confuses me; I'm confused why value isn't used for get and set instead. The get seems like it'll throw undefined if it wasn't for the if statement at the end of the function (if (value !== undefined) object[name] = value;)

The existence of that particular if statement actually also confuses me because it seems like it should be a conditional statement inside of get instead of being outside the function.

For additional educational purposes, why wouldn't a data property attribute definition wouldn't work?

Within the privateVariable function, the following code snippet probably would work seemingly:

Object.defineProperty(object, name, {
  enumerable: true,
  configurable: false,
  value: value,
  writable: true
}

// value could also take a function that can return value after conditional statements, like checking if it's undefined to perhaps throw an exception or something...
Avatar

Love this series! It's a great introduction to some of the more complex (for me) Angular topics.

I'm following along, and have run into two issues I'm confused by. Not sure if they're bugs or misunderstandings on my part or what.

The first issue has to do with the previous video in the series, in which you define a base-class spec which stubs out a SomeBaseClass and tests that Post can inherit it:

describe('BaseClass', function(){
  describe('Inheritance', function(){
    beforeEach( function() {

      Post.inherits(SomeBaseClass);

      function SomeBaseClass(attributes) {
        var _constructor = this;
        var _prototype   = _constructor.prototype;

        _constructor.new = function(attributes) {
          var instance = new _constructor(attributes)
          return instance;
        };

        _prototype.$save = angular.noop;
    });

    it 'adds methods to the child class', function(){
      expect(Post.new).toBeDefined();
    )};

    it 'adds methods to the instances', function(){
      post = Post.new({});
      expect(post.$save).toBeDefined();
    )};
  });
});

The issue (for me) is that, because you do Post.inherits(SomeBaseClass) before defining SomeBaseClass, I run into this error:

TypeError: 'undefined' is not an object (evaluating 'baseclass.apply')`

I was able to fix the issue by moving Post.inherits(SomeBaseClass) after the definition of SomeBaseClass, but I don't know if that's right, given that it disagrees with your video/code, nor do I get how it could work in your video/code.

Second question has to do with your caching spec in this video.

You write:

expect(Post.cached).toEqual({});`

But when I do that I run into this error:

PhantomJS 1.9.7 (Mac OS X) BCCache adds a cache to the model FAILED
    Expected {  } to equal {  }.
    Error: Expected {  } to equal {  }.

It seems to have something to do with this, but isEqual is supposed to calculate object-equality on the basis of key-value similarity, not reference. Maybe it has to do with the {} object's not having a constructor with Cache?

Am I doing something wrong? Can you think of a way to get around this imperfect equality issue in the spec?

Thanks,

Sasha

In reply to egghead.io
Avatar
Brett Shollenberger

With the first problem--

The code you've posted will work (albeit it's missing a bracket that closes SomeBaseClass); I'm guessing you're using something similar but not exactly this code? The way this works is through function hoisting (http://designpepper.com/blog/drips/variable-and-function-hoisting).

If you're assigning the function to a variable, like below, it won't work:

X.inherits(MyFunction)
var MyFunction = function() {
}

But this will:

X.inherits(MyFunction)
function MyFunction() {
}

With the second problem--
I think this may be a bug on my part; you are correct about the way object equality is tested in Jasmine (https://github.com/pivotal/jasmine/blob/33641578e6f7d90be6155ea6dcb262b11a326fea/src/core/matchers/matchersUtil.js#L143).

The different constructor makes this untrue. You should get the correct results out of:

expect(Post.cached).toEqual(new Cache())
In reply to
Avatar

Thanks! Yeah. I'm using Coffeescript, which may be causing other problems (including why I missed a bracket in translating it back). Maybe Coffeescript automatically assigns the function to a variable?

Thanks for the prompt reply!

In reply to Brett Shollenberger
Avatar
Brett Shollenberger

Yes, you're correct about that as well. Coffeescript removes this behavior because of the ambiguities you're noticing.

You can read more in The Little Book on Coffeescript's chapter "The Bad Parts" under Function Definition (https://arcturo.github.io/library/coffeescript/07_the_bad_parts.html).

In reply to
Avatar
Grégory

The videos "full resource" do not open here. Codec?
All others are normal.

Avatar
Grégory

Sorry, my download manager converted the extension .zip for mp4

In reply to Grégory
Avatar
valery

Hi Guys,

First of all, thank you for your hard work.
Could you help me, I can't download source code for this and other lessons from this playlist.
When I click "available for download" link the browser starts downloading of related video instead of source code.

Thanks.

Instructor: In our previous video, we saw why caching is the first thing that we want to add to our base class.

Let's go ahead and make sure that our post inherits from base-class.base. base-class is going to be the name space that the base module, which is our actual base class, will live in. While we're here, what we're going to do is go ahead and let the post instantiate with attributes. Let's say this.id = attributes.id. This will be the primary key. Again, that's useful in caching.

Let's go ahead and write our first test. I've added a caching spec for us. The first thing is, it adds a cache to the model. We want to expect post.cached should equal an empty object. It will start out as an empty object. Here we see undefined. We expect it to equal an empty object. That's because post doesn't yet have a cached property.

Let's go ahead and create the cache. In a previous video, you saw that we created this constructor and prototype on one of our mocks. Here we added it to our actual base class. What we're going to say is that the constructor, which will be the post model, dot cached equals new cache. We're going to create a cached model here. That will be called bc-cache, and we'll have that live in its own file. We'll inject it here.

Now we see. We have no bc-cache provider. Let's go ahead and create a home for our cache. Again, we're going to add a cache folder and a cache file. This is, real quick, going to be in the module base class. We're going to create a factory called bc-cache because that's what we called it. All we need to do here is to create a constructor for our cache and go ahead and return that constructor. Now our tests pass.

Let's go ahead and create our next test here. We'll copy this guy out. We're going to expect that it adds new instances to the cache when they are created. We're going to say that, when we call post.new with an ID of one, then we expect post.cached to equal post. If we look up the first post, it should be this post with the ID of one. We see that this fails because we don't have a post.new method.

Let's go ahead and add that method. I'm going to bring in some code that we created in a previous video where we add this new method to the constructor. Now we're going to have a different error. We expected undefined to equal the first post with an ID of one.

We want to go ahead and cache that instance. To do that, we have to create a cache method which takes an instance. This will abstract some of the functionality here of the constructor.cached. We'll add a cache method to that, which will take an instance and a primary key.

The primary key will be defined on the constructor. This will allow the user to say the primary key is ID or it's _ID. We'll give it a default in a second. That will look up the instance.ID, which will be the primary key out of the box. It will say cache the instance with ID of one or return the instance with ID of one.

Here we se that we don't have a cache method on the cache. Also, we're going to need to add this constructor.primary-key because otherwise that will be undefined.

This primary key we're going to want to define is a private variable that will exist on the constructor like the post model, but we don't want it to be a enumerated over because we don't want users of our base class to see it.

What we're do here is abstract what it means to have a private method into a global function here. We're going to take the name of an object, the name of an attribute, and then a value to assign to that. That will create our private variable for us. This is adding some boilerplate for us.

What we're going to here is, we're going to say "private variable." We're going to add this to the constructor, and we're going to call it primary-key. This will be the value of the primary key out of the box. We'll say that it's ID because that's what most primary keys are.

Again, we're still seeing that we don't have a cache method on the cached object. Let's go ahead and add this to our cache here. This is going to be a private variable as well because we don't want it to be exposed on the cache itself. We'll say "cache instance." It will take a primary and an instance.

If the instance exists and the primary key of that instance is not equal to undefined -- meaning it could be zero, it could be one, anything else -- then we're going to want to add the number as the key. Instance primary key is equal to this instance. That will go ahead and add it to the cache. We see this makes it pass.

The final thing we want is for our cache to be queryable. We want to say that if we have a post that we've added here, and we search for it, and we say where the ID equals one, we want it to return an array of post, where post is the only object in there. If we queried for other things, like maybe the title of the post or the author of the post as a certain thing, we might want this to be a number of objects where it's post one, two, and three, something along those lines.

We want it to find items in the cache via parameters that we pass in. This is going to be really useful for us in the future. Here we see we have no method "where."

Let's go ahead and make this pass. We're going to do this using LoDash. We're going to define the final private variable. It's going to be called "where." Again, it's going to be a function. This function is going to take some search terms.

We're going to return LoDash.where. This has the terms. We're going to pass in "this is the context." Again, this is going to run a simple query on that object, and find the cached instance where the search parameters are true.

Now we've added caching to our base class. We've thoroughly tested it. Now it's time to move on to the next video and keep building.

HEY, QUICK QUESTION!
Joel's Head
Why are we asking?