AngularJS $scope always convert member instances to object - javascript

I have this simple code and Im used to organized my class, services etc.
now my problem is that why angular $scope always changes all member instances to object?
$scope.prepareCreateCustomer = function() {
$scope.customer = new Customer({id:'',email:'',firstName:'',lastName:''});
alert($scope.customer instanceof Customer); // this returns true!**
}
$scope.saveCustomer = function () {
alert($scope.customer instanceof Customer); // this return false!**
$scope.customer = customerService.createCustomer($scope.customer);
if ($scope.customer.errorMsg) {
alert($scope.customer.errorMsg);// this gets invoke as in service i have some logic**
} else if ($scope.customer == null) {
alert(Null);
} else {
alert($scope.customer.id + " " + $scope.customer.firstName + " " +$scope.customer.lastName);
}
}
it puts me to confusion of what happen under beneath AngularJS and why instances get changes to its
supertype? does this has something to do with AngularJS directives invoking the $scope?
My aim is to ensure that my argument as i save an object is to check if its the right instance but in my case it always return false as in Angular convert my Customer instance to an object.
ADDITIONAL INFO:

Related

Breeze JS - Extending model from object graph

Currently I am able to extend the breeze model entity type of my Product entity using the following code:
function registerProduct(metadataStore) {
function Product(){}
// description property
Object.defineProperty(Product.prototype,'description', {
// TODO: handle selected globalization language
get: function () {
return this.descripFr;
}
})
metadataStore.registerEntityTypeCtor('Product',Product);
}
The issue I am having is using a property from the entity graph (in this case codeligne) in an extended property like so:
Object.defineProperty(Product.prototype,'name', {
get: function () {
var codeligne = this.codeligne;
var name = codeligne.ligne + '-' + this.codeprod;
return name;
}
})
This throws an undefined exception for codeligne.ligne.
If I directly use the codeligne.ligne in an ng-repeat then the property is displayed properly so Breeze seems aware of it.
Any suggestions on how to use the codeligne graphed object when extending the model?
It is possible that the entity represented by the "ligne" navigation property isn't loaded yet. You should check that it contains a value before referencing its properties.
Object.defineProperty(Product.prototype, 'name', {
get: function () {
var codeligne = this.codeligne;
if (!codeligne) {
return null;
}
var name = codeligne.ligne + '-' + this.codeprod;
return name;
}
})

Initialize Backbone Models based on another Backbone Model

I have a config.json that I am going to load into my app as a Backbone Model like:
var Config = Backbone.Model.extend({
defaults: {
base: ''
},
url: 'config.json'
});
Other models should be dependent on some data contained in Config like:
var ModelA = Backbone.Collection.extend({
initialize: function(){
//this.url should be set to Config.base + '/someEndpoint';
}
});
In above example, ModelA's url property is dependent on Config's base property's value.
How do I go about setting this up properly in a Backbone app?
As I see it, your basic questions are:
How will we get an instance of the configuration model?
How will we use the configuration model to set the dependent model's url?
How can we make sure we don't use the url function on the dependent model too early?
There are a lot of ways to handle this, but I'm going to suggest some specifics so that I can just provide guidance and code and "get it done," so to speak.
I think the best way to handle the first problem is to make that configuration model a singleton. I'm going to provide code from backbone-singleton GitHub page below, but I don't want the answer to be vertically long until I'm done with the explanation, so read on...
var MakeBackboneSingleton = function (BackboneClass, options) { ... }
Next, we make a singleton AppConfiguration as well as a deferred property taking advantage of jQuery. The result of fetch will provide always(callback), done(callback), etc.
var AppConfiguration = MakeBackboneSingleton(Backbone.Model.extend({
defaults: {
base: null
},
initialize: function() {
this.deferred = this.fetch();
},
url: function() {
return 'config.json'
}
}));
Now, time to define the dependent model DependentModel which looks like yours. It will call AppConfiguration() to get the instance.
Note that because of MakeBackboneSingleton the follow is all true:
var instance1 = AppConfiguration();
var instance2 = new AppConfiguration();
instance1 === instance2; // true
instance1 === AppConfiguration() // true
The model will automatically fetch when provided an id but only after we have completed the AppConfiguration's fetch. Note that you can use always, then, done, etc.
var DependentModel = Backbone.Model.extend({
initialize: function() {
AppConfiguration().deferred.then(function() {
if (this.id)
this.fetch();
});
},
url: function() {
return AppConfiguration().get('base') + '/someEndpoint';
}
});
Now finally, putting it all together, you can instantiate some models.
var newModel = new DependentModel(); // no id => no fetch
var existingModel = new DependentModel({id: 15}); // id => fetch AFTER we have an AppConfiguration
The second one will auto-fetch as long as the AppConfiguration's fetch was successful.
Here's MakeBackboneSingleton for you (again from the GitHub repository):
var MakeBackboneSingleton = function (BackboneClass, options) {
options || (options = {});
// Helper to check for arguments. Throws an error if passed in.
var checkArguments = function (args) {
if (args.length) {
throw new Error('cannot pass arguments into an already instantiated singleton');
}
};
// Wrapper around the class. Allows us to call new without generating an error.
var WrappedClass = function() {
if (!BackboneClass.instance) {
// Proxy class that allows us to pass through all arguments on singleton instantiation.
var F = function (args) {
return BackboneClass.apply(this, args);
};
// Extend the given Backbone class with a function that sets the instance for future use.
BackboneClass = BackboneClass.extend({
__setInstance: function () {
BackboneClass.instance = this;
}
});
// Connect the proxy class to its counterpart class.
F.prototype = BackboneClass.prototype;
// Instantiate the proxy, passing through any arguments, then store the instance.
(new F(arguments.length ? arguments : options.arguments)).__setInstance();
}
else {
// Make sure we're not trying to instantiate it with arguments again.
checkArguments(arguments);
}
return BackboneClass.instance;
};
// Immediately instantiate the class.
if (options.instantiate) {
var instance = WrappedClass.apply(WrappedClass, options.arguments);
// Return the instantiated class wrapped in a function so we can call it with new without generating an error.
return function () {
checkArguments(arguments);
return instance;
};
}
else {
return WrappedClass;
}
};

angularFire 3 way data binding won't update an function

I have a firebaseObject (MyFirebaseService.getCurrentUser()) bind to $scope.user.
After binding successful, I loop tho the object to see if the object contain "associatedCourseId" equal to some value ($stateParams.id). If does, the $scope.finishLessonCount count up. The problem is, when I add new Object inside the firebaseObject (that bindto user) via other page OR inside firebase, the finishLessonCount value won't change as what I expect for 3 way binding. I need to refresh the page to see the finishLessonCount reflect the true value. What is wrong? I want the finishLessonCount change using the compare function as I add more finishedLessons into the firebaseObject. Please see code below:
MyFirebaseService.getCurrentUser().$bindTo($scope, "user").then(function(){
for (var key in $scope.user.finishedLessons) {
if ($scope.user.finishedLessons.hasOwnProperty(key)) {
if ($scope.user.finishedLessons[key].associatedCourseId == $stateParams.id) {
$scope.finishLessonCount++;
}
}
};
console.log ($scope.finishLessonCount);
});
UPDATE 1 according to #Kato solution:
I decide to use Extending firebaseOject way to solute this problem. But still, it does not. I did not use factory here to simplify thing since I need to pass in courseId to do the operation. Here is my code:
function countLessons(lessons, courseId) {
var count = 0;
for(var key in lessons) {
if( lessons[key].associatedCourseId == courseId) {
count++;
}
}
return count;
}
var UserWithLessonsCounter = $firebaseObject.$extend({
$$updated: function(snap) {
var changed = $firebaseObject.prototype.$$updated.call(this, snap);
this.lessonCount = countLessons(this.finishedLessons, $stateParams.id);
}
});
var refTemp = new Firebase($rootScope.baseUrl + "users/" + $rootScope.userId);
var userTemp = new UserWithLessonsCounter(refTemp);
userTemp.$bindTo($scope, "userTemp").then(function(){
console.log($scope.userTemp);
});
userTemp.$watch(function() {
console.log("Does this run at all? " + $scope.userTemp.lessonCount);
});
I update the user object, the lessonCount value did not change unless I refresh the page. And the console.log inside $watch did not run at all. What is wrong?
The promise returned by $bindTo is called exactly once. It's not an event listener. You can't listen to this to get updated each time there is a change.
Please read the guide, start to finish, and read about Angular's $watch method before continuing down this route, as with some fundamental knowledge, this should not have been your first instinct.
A beginner approach would be to utilize $watch:
MyFirebaseService.getCurrentUser().$bindTo($scope, "user");
$scope.$watch('user', function() {
for (var key in $scope.user.finishedLessons) {
if ($scope.user.finishedLessons.hasOwnProperty(key)) {
if ($scope.user.finishedLessons[key].associatedCourseId == $stateParams.id) {
$scope.finishLessonCount++;
}
}
};
console.log ($scope.finishLessonCount);
});
Or, having familiarized with the AngularFire API, one might pick $scope.user.$watch() in place of the scope method, which would prove more efficient.
Having written a large portion of the AngularFire code, I would pick the $extend tool, which was added precisely for use cases like this:
// making some assumptions here since you haven't included
// the code for your firebase service, which does not seem SOLID
app.factory('UserWithLessonsCounter', function($firebaseObject) {
return $firebaseObject.$extend({
$$updated: function(snap) {
var changed = $firebaseObject.prototype.$$updated.call(this, snap);
this.lessonCount = countLessons(this.finishedLessons);
return changed;
}
});
});
function countLessons(lessons) {
var count = 0;
for(var key in lessons) {
if( lessons.hasOwnProperty(key) ) {
count++;
}
}
return count;
}
And now in your controller:
app.controller('...', function($scope, UserWithLessonsCounter) {
var ref = new Firebase(...);
var user = new UserWithLessonCounter(ref);
user.$bindTo($scope, 'user');
user.$watch(function() {
console.log($scope.user.lessonCount);
});
});

Adding a function to $resource prototype

I'm trying to add a view function to an Angular $resource. I add it to the $resource via prototype but for some reason the 'this' reference in the prototype function is incorrect so all of the properties are undefined. Strangely though in console.log the this seems to have all the properties it would need to return correctly.
http://plnkr.co/edit/YsTlAztjEKjn3piQAem2?p=preview
app.factory("Now", function($resource) {
var Now = $resource("http://date.jsontest.com/");
Now.prototype.$dateTime = function() {
console.log("2", this); // this has date and time properties, good
return this.date + " " + this.time;
};
return Now;
});
app.controller("TestController", function(Now) {
var now = new Now();
now.$get();
console.log("1", now); // prototype has the $dateTime function!
console.log("3", now.$dateTime()); // but it returns undefined, bad
});
Actually, your error is that your calling $dateTime before the resource returns with the data.
See this plunk
The only reason the answer above me appears to work is it is being interpolated, and when the resource finally returns, the datetime function is called AGAIN. But if you were to leave the code the same, it would still fail
You're attempting to use the properties of this while the model is still being fetched from the server which is resulting in those properties being undefined when you ask for them. You need to use the promise methods that are available on the object returned from $get in order to guarantee that the request has finished.
This
now.$get();
Should become this
now.$get().then(function() {
console.log("1", now);
console.log("3", now.$dateTime());
});
Your error is in using .factory. It expects an object to be returned, but $resource is a function. Change it to .service and it will work
app.service("now", function($resource) {
complete code: http://plnkr.co/edit/n31MMNTlKV3i04HdXUms?p=catalogue
var app = angular.module("test", ["ngResource"]);
app.service("now", function($resource) {
var now = $resource("http://date.jsontest.com/");
now.prototype.dateTime = function() {
return this.date + " " + this.time;
};
return now;
});
app.controller("TestController", function($scope, now) {
$scope.now = now.get();
});

When to add extend additional complex types onto a Breeze entity

I want to load an entity from the database, and then using it's ICollection (from the model class) load up some child data. This would be simple enough to do from individual view models if it was a 1 to many relationship, but I have a bit more complex structure -
Parent has many children. Each child has many grandchildren, that need to be linked back to the proper child. The hierarchy needs to remain in tact.
The other options that I have come up with so far may not be the best way so I my question is - what is the best practice to load up the grandchildren - or some other method?
in a constructor while configuring the metadataStore -
function configureMetadataStore(metadataStore) {
metadataStore.registerEntityTypeCtor(
'Child', null, childInitializer);
}
function childInitializer(child) {
child.grandchildren = (Do something here)
return grandchildren;
});
}
In the viewmodel where children are being populated -
function refresh() {
return datacontext.getChildren(childs, parentId);
}
var addGrandChildren = function () {
$.each(childs, function (i) {
var grandChildren = ko.observableArray();
var childId = $(this).data(id);
datacontext.getGrandChildren(grandChildren, childId);
});
return;
};
Or some other method?
Providing that your relationships are not unidirectional, Breeze entities will automatically hook themselves together when you query them. ( Edit: as of v 1.3.5 - breeze will also hook up unidirectional relations as well. )
This means that if you use a query to extract n entities that just happen to be related all of them will be automatically linked to one another in the correct fashion. The same occurs if you use the EntityQuery.expand method. So your issue is simply how to query for whatever portion of the graph you want in the least number of calls.
Note: you should also look at the EntityAspect.loadNavigationProperty method if you really want to actually "walk" the graph. But this can be non-performant if you are dealing with large graphs.
I have the same issue with Breezejs (1.4.2) q (0.9.7)
I want to add a computed property for an entity.
var doctorInitializer = function (doctor) {
doctor.FullName = ko.computed(function () {
return doctor.FirstName() + " " + doctor.MiddleName() + " " + doctor.LastName() + " " + doctor.SurName();
});
};
var doctorName = '/breeze/polyclinic',
doctorManager = new breeze.EntityManager(doctorName);
var store = doctorManager.metadataStore;
store.registerEntityTypeCtor("Doctor", null, doctorInitializer);
i try adding a knockout computed to the constructor
var doctor = function () {
self.FullName = ko.computed( {
read: function() {
return self.FirstName + " " + self.MiddleName + " " + self.LastName + " " + self.SurName;
},
deferEvaluation: true
});
};
store.registerEntityTypeCtor("Doctor", doctorInitializer);
in both cases only work if i remove the parenthesis but MiddleName and SurName is not required and instead of empty string i got null
this is the error i have http://screencast.com/t/bP9Xnmf9Jm

Categories