Angular Meteor $reactive vs getReactively - javascript

I'm learning Angular Meteor and I have a question:
What is the difference between using $reactive and getReactively?
If you take a look at the API reference you get this for $reactive (http://www.angular-meteor.com/api/1.3.2/reactive):
A service that takes care of the reactivity of your Meteor data, and updates your AngularJS code.
This service wraps context (can be used with this or $scope) - so you can use it with any context as you wish
And this for getReactively (http://www.angular-meteor.com/api/1.3.2/get-reactively):
Use this method to get a context variable and watch it reactively, so each change of this variable causes the dependents (autorun, helper functions and subscriptions) to run again.
The getReactively method is part of the ReactiveContext, and available on every context and $scope.
As far as I could understand $reactive will make everything reactive ('this', '$scope' and son on as long as you apply it to them) and getReactively will make only that particular variable, or object reactive.
So if I make this:
controller: function ($scope, $reactive) {
var vm = $reactive(this).attach($scope);
vm.sort = {
name: 1
};
this.helpers({
parties: () => {
return Parties.find({}, { sort : vm.sort });
}
});
this.subscribe('parties', () => {
return [{
sort: vm.sort
},
this.getReactively('searchText')
]
});
});
Why don't I get the same result as if I was doing this:
controller: function ($scope, $reactive) {
var vm = $reactive(this).attach($scope);
vm.sort = {
name: 1
};
this.helpers({
parties: () => {
return Parties.find({}, { sort : this.getReactively('sort') });
}
});
this.subscribe('parties', () => {
return [{
sort: this.getReactively('sort')
},
this.getReactively('searchText')
]
});
});
If $reactive takes care of reactivity I was expecting to see anything inside this and $scope to be reactive and, like in getReactively, whenever something is changed, to cause its dependents to run again.
So: what am I missing?

Related

Angular 1.6 bindings inside controller

Im trying to pass some parameters to my component through bindings, but unfortunately I'm not having luck in using those params in my controller, this is my code:
angular.module('project1').component('menu', {
templateUrl: '/static/js/templates/menu.template.html',
bindings: {
rid: '#'
},
controller: ['Restaurant', function RestaurantListController(Restaurant) {
console.log(this.rid);
console.log(this);
this.restaurant = Restaurant.get({restaurantId: this.rid});
}]
});
HTML component:
<menu rid="1"></menu>
The interesting thing is that i can access the parameters in the template and when i do the 2 console log, the first one is undefined, but in the second one i can see the rid variable...so, i really don't understand what i'm missing.
With angular 1.6, your bindings are going to be ready on the method $onInit and not before.
If you need to re-enable auto bindings
https://toddmotto.com/angular-1-6-is-here#re-enabling-auto-bindings
If anyone still searching for solution, use $onInit method provided by angular.
this.$onInit = function () {
$http.get(`/api/v1/projects`).then((res) => {
$scope.projects = res.data;
}, (err) => {
$scope.error = err
})
};

Parameter passing in angularjs

I am learning Angular and trying to dissect the following code:
phone.js
angular
.module('phone', [])
.factory('phoneService', phoneService);
function phoneService() {
var service = {
getAll: getAll
};
function getAll() {
phones = [
{
id: 0,
name: 'Luke Skywalker',
number: 111-222-333,
},
{
id: 1,
name: 'R2D2',
number: 111-222-444,
},
]
return phones;
}
return service;
}
app.js
angular
.module('app', ['phone'])
.controller('AppController', AppController);
function AppController ($scope, phoneService) {
$scope.phones = phoneService.getAll();
$scope.title = "Customer Phones";
}
I am intrigued by the usage of $scope and phoneService. They are references to actual variables outside the AppController. $scope is the variable that AngularJS supplies and phoneService is a reference to the factory name in phone.js. I found that they must be named exactly the way they are, otherwise they won't be references to the actual ones with that names. Here is a link to plunker code.
I do not understand how this works behind the scenes. I tried to create a sample JavaScript to see if I can pass a reference to a global variable to a function and then access it inside the function. As expected it thinks the variable is undefined.
var i = {name: 'Bob'};
TestHello(Hello);
function Hello(i)
{
document.writeln("name is --" + i.name);
}
function TestHello(K)
{
K();
}
Link to test the above code at js.do code
So, my question is what makes AngularJS code link the variables in an outside scope to the ones passed with the same names as the parameters?

Angular Directives performance issue

I have a .NET webservice that returns an object like:
myObj = {
prop1: value,
prop2:value,
...
prop5:value
}
I created an angular service that returns this entire object(myObj).
I created 5 distinct directives to display these properties in different pages in the application(sometimes, some of them can be in the same page).
I'm calling the angular service in these directives, creating for any of them this "link" function:
link: function (scope, element, attrs) {
getService.getMethod().$promise.then(
function (myObj) {
element.text(myObj.prop1); // .prop2, ... , prop5
},
function (statusCode) {
console.log(statusCode);
}
);
}
I have the feeling that my approach is not the best, calling five times the angular service(through the $promise) obtaining actually the same object(myObj).
If you are interested also how the service is looking:
var localResource = $resource('https://.....',
{},
{'getAll': {method: 'JSONP', isArray: false, params: {callback: 'JSON_CALLBACK'}}}
);
return {
getMethod: function () {
return localResource.getAll();
}
}
Please help, if someone has an ideea haw can I improve it.
Thank you!
It sounds like you can make your getMethod issue only one server call.
.factory("getService", function () {
var getAll;
return {
getMethod: function () {
if (!getAll) {
getAll = localResource.getAll();
}
return getAll;
};
};
});

How do I unit test $scope.broadcast, $scope.$on using Jasmine

I'm newbie to AngularJs/NodeJs world, so forgive if this is a basic question to some.
So in a nutshell I've two controllers, the first controller $broadcast an 'Id' and the second controller fetches that Id with $on and then passes that Id to an intermediate service, which makes an $http ajax call and returns a single Book object.
How do I unit test $scope.broadcast, $scope.$on using Jasmine
firstCtrl
.controller('firstCtrl', function($scope, ...){
$scope.selectGridRow = function() {
if($scope.selectedRows[0].total !=0)
$scope.$broadcast('id', $scope.selectedRows[0].id);//Just single plain ID
};
});
secondCtrl
.controller('secondCtrl',
function($scope, bookService) {
$scope.$on('id', function(event, id) {
bookService.getBookDetail(id).then(function(d) {
$scope.book = d.book;
});
});
});
expected Json obj
var arr = "book" : [ {
"id" : "1",
"name" : "Tomcat",
"edition" : "9.1"
}
]
Let me know if anyone wants me to post the $http service that's used by the second controller.
expected behavior
So from the top of my head, ideally, I would like to test every possible scenario, but something like below, which can then expend:
expect(scope.book).toEqual(arr);
expect(scope.book).not.toEqual(undefined);
Thanks everyone!
First you should do the broadcast on $rootScope then you can receive on $scope.
Now to the testing. I assume you want to include real request to your API via bookService and $http. This can be mocked but I'll focus on the real call. Let me know if you need the mocked one.
Before the actual test, you will need to do some injections/instantiations:
Initialize your app
Inject $controller, $rootScope, $httpBackend and bookService
Create scopes for firstController and SecondController and store it in a variable
Store bookService and $httpBackend in variables
Instantiate the controllers and store them
Then in the actual test you must tell $httpBackend what to do when it caches request for the books (or books). Construct $httpBackend.whenGET("/api/books/1").passThrough(); will pass request with url "/api/books/1" to the server.
Next your must setup property selectedRows on firstScope so it fulfills the condition in function selectGridRow in your firstCtrl.
Now you can call function selectGridRow to trigger the broadcast and API call. But you must wrap it in runs function so Jasmine recognizes this as an async call and will wait for it to finish. The 'waiting' is defined in waitsFor call. It will wait until it gets a book and it waits max 5000 ms then the test will be marked as failed.
Last step is to check expected result. We don't have to check for undefined anymore as the test would not get to here anyway. The check must be wrapped again runs call so it is executed afters successful 'waitsFor'.
Here is the full code:
describe("Broadcast between controllers", function () {
beforeEach(module('app')); //app initialization
var firstScope;
var secondScope;
var bookService;
var $httpBackend;
var firstController;
var secondController;
beforeEach(inject(function ($controller, $rootScope, _bookService_, _$httpBackend_) {
firstScope = $rootScope.$new();
secondScope = $rootScope.$new();
bookService = _bookService_;
$httpBackend = _$httpBackend_;
firstController = $controller('firstCtrl', { $scope: firstScope });
secondController = $controller('secondCtrl', { $scope: firstScope, bookService: bookService });
}));
it("should work", function () {
$httpBackend.whenGET("/api/books/1").passThrough();
firstScope.selectedRows = [{ id: 1, total: 1000 }];
secondScope.book = null;
runs(function () {
firstScope.selectGridRow();
});
waitsFor(function () {
return secondScope.book != null;
}, "Data not received in expected time", 5000);
runs(function () {
expect(secondScope.book[0].id).toEqual(1);
});
});
});

Meteor collection always returns undefined on client

Im new to meteor and have been trying to learn the framework via the discover meteor book. Im having a few issue understanding what exactly is going on in my application (found here https://github.com/Themitchell/Sound-wav.es).
Essentially, my understanding is that on my server side I allow publications for certain collections which take arguments from my client side subscribe calls. For this part on my server i have this in my server/publications.js file:
Meteor.publish('studios', function (options) {
return Studios.find({
userId: this.userId
}, options);
});
Meteor.publish('studio', function (id) {
return id && Studios.find({
userId: this.userId,
_id: id
});
});
Next we would need a controller to handle the routing and deal with waiting for any subscriptions required, then, once the subscriptions are ready (hence the waitOn) go and render the template providing the data function as a reactive data source for the templates.
So I then set up my 2 routes for indexing studios and showing one studio using iron router and 'Controllers' as follows:
StudiosIndexController = RouteController.extend({
template: 'studiosIndex',
increment: 20,
limit: function () {
return parseInt(this.params.studiosLimit) || this.increment;
},
findOptions: function () {
return {
sort: this.sort,
limit: this.limit()
};
},
waitOn: function () {
return Meteor.subscribe('studios', this.findOptions());
},
studios: function () {
return Studios.find({}, this.findOptions());
},
data: function () {
return {
studios: this.studios()
};
}
});
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
Router.map(function () {
this.route('studiosIndex', {
path: '/',
controller: StudiosIndexController
});
this.route('showStudio', {
path: '/studios/:_id',
controller: ShowStudioController
});
});
Now this is great and works fine when displaying my index page. I get a list of collections which is reactive and the as i introduce new studios to the collection i see them get created on the server and on the client respectively. However in my show view when the view is rendered the data always seems to be empty. On dropping into my show controller's data function with a debugger I tried querying the Studios and this always returns undefined even when i try to fetch everything possible. Oddly I know that my publications are logging the correct id for a studio (using console.log). It seems that i get all the correct data up until the routing on the client side. The parameter ids are all correct but a find() call on studios always returns nothing. Am I missing something obvious here.
Its also worth noting i deleted my helpers for 'studios' and 'studio' in views/studios/index.js and views/studios/show.js respectively as my understanding is that this is what im doing with studios and studio in the controller. These helpers are now defined at the controller level and passsed to the reactive 'data' function. IS this correct?
TL;DR
With the code above my index action works however my show action fails where the data function always returns undefined and in fact on the show page i cannot get access to any of my studio information (Studios.find({}).count() always returns 0). I'm unsure how the 2 routes differ. Can anyone spot the issue?
Edit: Its also worth noting having looked through some of the iron router issues on github i have tried using this.ready(). The first time the route is run i hit data and this is false but then even wrapping my data helpers to wait for this.ready() gets an undefined return value when this.ready() returns true.
Extra Notes
Im running meteor 0.8.0 with latest 0.7.0 release of iron router and collection2 with simple schema, just in case you are interested / require this info.
EDIT: Possible solution
So having fiddled around it seems like my helpers are the issue. By using the 'studio' section and then calling this.studio() inside my data function this seems to break. If I change the code below:
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
studio: function () {
return Studios.findOne(this.params._id);
},
data: function () {
return {
studio: this.studio()
};
}
});
to this
ShowStudioController = RouteController.extend({
template: 'showStudio',
waitOn: function () {
return Meteor.subscribe('studio', this.params._id);
},
data: function () {
return Studios.findOne(this.params._id);
}
});
My view renders correctly again. Im unsure where i saw this pattern however I had assumed the function assigned to 'studio' was essentially the same as doing
Template.showStudio.helpers({
studio: function () {
return Studios.findOne(this.params._id)
}
});
but it seems not!

Categories