Angular Meteor controllerAs getReactively - angularjs

The config function where the controller as is defined:
function config($stateProvider){
$stateProvider
.state('games', {
url: '/games',
templateUrl: 'client/games/games.ng.html',
controller: 'Games',
controllerAs: 'vm'
});
}
My controller:
function GamesController($scope, $meteor, Engine) {
var vm = this;
vm.selectedMove = { _id: 'dsfsdf'}
vm.evaluations = $meteor.collection(Evaluations),
$meteor.subscribe('evaluations', {}, $scope.getReactively('vm.selectedMove')._id).then(function(){
console.log(vm.evaluations);
});
}
I get an error message:
"Cannot read property '_id' of undefined", and it points to this line:
'$scope.getReactively('vm.selectedMove')._id).then...'
What am I doing wrong?
Thanks in advance!

The value bound by $scope.getReactively may not always be present. In this instance you are calling getReactively while constructing your controller. So $scope.vm hasn't been set yet. Hence getReactively will return undefined.
You can try $scope.getReactively('vm.selectedMove._id') and check for undefined. For example:
this.query = {q : '' };
this.list = $scope.$meteorCollection(Participants);
$scope.$meteorAutorun(function() {
var q = $scope.getReactively('participants.query.q');
$scope.$meteorSubscribe('participants', q || '')
});
Also note that the whole thing has to be wrapped in autorun.

Related

Unable to inject my service in Angular resolve inside router configuration

(My plunkr code resides at http://plnkr.co/edit/6KU3GblQtMdRAx3v3USV?p=preview)
I'm trying to create a Search bar (in navigation) which should ultimately hit the backend REST API. The input search button when clicked on input 'alpha' would trigger a route to products/search/0?searchtext=alpha
Clicking on the button triggers a route change, which should do resolve as follows:
.when("/products/search/:page", {
templateUrl: "products.html",
controller: "ProductsSearchController",
resolve: {
// Define all the dependencies here
ProdSearchServ : "ProdSearchService",
// Now define the resolve function
resultData : function(ProdSearchServ) {
return ProdSearchServ.searchProducts();
}
}
})
However, I'm getting the following error
angular.js:9784 Error: [$injector:unpr] Unknown provider: ProdSearchServProvider <- ProdSearchServ
I believe I'm doing most of the things as per conventions, may be I'm missing something here?
I'm copying script.js code below (also in plnkr link above). It has all the route configuration and the controllers defined.
(function(){
// jargoViewer Create a new Angular Module
// This would go into the html tag for index.html
var app = angular.module("jargoViewer", ["ngRoute"]);
app.config(function($routeProvider){
$routeProvider
.when("/main", {
templateUrl: "main.html",
controller: "NavController"
})
.when("/products/search/:page", {
templateUrl: "products.html",
controller: "ProductsSearchController",
resolve: {
// Define all the dependencies here
ProdSearchServ : "ProdSearchService",
// Now define the resolve function
resultData : function(ProdSearchServ) {
return ProdSearchServ.searchProducts();
}
}
})
.otherwise({redirectTo:"/main"});
});
}());
// Nav Controller
(function() {
var app = angular.module("jargoViewer");
var NavController = function($scope, $location) {
// Function to invoke the Prod search based on input
$scope.search = function() {
console.log("searchText : " + $scope.searchtext);
$location.path("products/search/0").search({searchtext: $scope.searchtext});
};
};
app.controller("NavController", NavController);
}());
// Define the Prod Search Service here
(function() {
// Get reference to the app
var app = angular.module("jargoViewer");
// Create the factory
app.factory('ProdSearchService', function($routeParams, $http, $q) {
var searchProducts = function() {
pageNum = 0;
searchParam = '';
if (('page' in $routeParams) && (typeof $routeParams.page === 'number')) {
pageNum = $routeParams.page;
}
// Check if the router Param contains the field searchtext, if so, check if its a string
if (('searchtext' in $routeParams) && (typeof $routeParams.searchtext === 'string')) {
searchParam = $scope.routeParam.searchtext;
}
// Now make the http API hit to fetch the products
var request = $http({
method: "get",
url: "http://abcd.com/products/search/" + pageNum,
params: {
search: searchParam
},
});
return(request.then(handleSuccess, handleError));
};
function handleError(response) {
// The API response from the server should be returned in a
// nomralized format. However, if the request was not handled by the
// server (or what not handles properly - ex. server error), then we
// may have to normalize it on our end, as best we can.
if (
! angular.isObject(response.data) ||
! response.data.message
) {
return($q.reject( "An unknown error occurred."));
}
// Otherwise, use expected error message.
return($q.reject(response.data.message));
}
// I transform the successful response, unwrapping the application data
// from the API response payload.
function handleSuccess(response) {
if(response.data.error == true) {
return($q.reject(response.data.message));
}
return(response.data.data);
}
return {
searchProducts : searchProducts
};
});
}());
// Define the Products Search Controller below
(function() {
var app = angular.module("jargoViewer");
//var ProductController = function($scope) {
var ProductsSearchController = function($scope, $routeParams, ProdSearchService) {
// Nothing to do really here
};
app.controller("ProductsSearchController", ProductsSearchController);
}());
This caused by your bizarre naming conventions. Sometimes ProdSearchServ and sometimes ProdSearchService.
If you just pick one and use it consistantly then you won't run into these types of errors.
Fixed Plunker
In particular you create the service with the name ProdSearchService and then attempt to use it with a different name:
app.factory('ProdSearchService',
//vs
resultData : function(ProdSearchServ) {
I imagine you we under the impression that this code would fix it for you. However, this only applies to dependencies passed into the controller, not functions in general. For services which already exist, you do not need to define them specially like this; instead simply use the correct name in the controller.
// Define all the dependencies here
ProdSearchServ : "ProdSearchService",
I think you don't need to define the dependency when you say
// Define all the dependencies here
ProdSearchServ : "ProdSearchService",
Just do this:
.when("/products/search/:page", {
templateUrl: "products.html",
controller: "ProductsSearchController",
resolve: {
resultData : function(ProdSearchService) { //as you defined it before
return ProdSearchService.searchProducts();
}
}
})
There is a similar question here

Jasmine testing controller from wrong ui-router state

I have a page where everything works fine. However, my test for this page's controller FeeRuleCtrl, after it tests the code of said controller, goes on and starts testing the controller of a different state. Here's my app.js:
$stateProvider
.state('root', {
url: "/",
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/root') %>",
controller: 'RootCtrl',
resolve: {
feeSuites: function(FeeSuiteCrud, FeeSuite){
console.log('here');
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule', {
abstract: true,
controller: 'FeeRuleCtrl',
template: "<ui-view/>",
resolve: {
feeTypes: function(FeeSuiteCrud, FeeType){
var feeCrud = new FeeSuiteCrud(FeeType)
var promise = feeCrud.query();
return promise.then(function(response){
return response;
})
},
feeSuites: function(FeeSuiteCrud, FeeSuite){
var feeCrud = new FeeSuiteCrud(FeeSuite);
var promise = feeCrud.query();
return promise.then(function(response){
return response;
});
}
}
})
.state('fee-rule.new', {
url: '/new',
controller: 'NewCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Add a New Fee Rule' }
})
.state('fee-rule.edit', {
url: "/edit/:id",
controller: 'EditCtrl',
templateUrl: "<%= Rails.application.routes.url_helpers.client_side_path('admin/fee_suites/feeRule.html') %>",
data: { title: 'Edit Fee Rule' },
resolve: {
feeRule: function(FeeSuiteCrud, FeeRule, $stateParams){
var feeCrud = new FeeSuiteCrud(FeeRule);
var promise = feeCrud.get($stateParams.id)
return promise.then(function(response){
return response;
});
}
}
});
I have an abstract state, fee-rule, because both the new and edit states share most of the same functionality.
When I go to the page's address, <host>/admin/fee_suites/new, I inspect the network tab and there are 4 server calls made:
api/v3/fee_types
api/v3/fee_suites
api/v3/fee_suites/8?association=fee_rules
api/v3/fee_types/9?association=fee_parameters
The first 2 are resolves in the fee-rule state. I take care of this like so in the test:
beforeEach(function(){
module(function($provide){
$provide.factory('feeSuites', function(FeeSuite){
feeSuite = new FeeSuite({
id: 8,
site_id: 9,
active: true
});
return [feeSuite];
});
$provide.factory('feeTypes', function(FeeType){
feeType = new FeeType({
id: 9,
name: 'Carrier Quotes',
value: 'carrier_quotes'
});
return [feeType];
});
});
inject(function($injector){
$rootScope = $injector.get('$rootScope');
$controller = $injector.get('$controller');
$httpBackend = $injector.get('$httpBackend');
scope = $rootScope.$new();
$controller("FeeRuleCtrl", {
'$scope': scope
});
});
});
The last 2 server calls are made inside FeeRuleCtrl. I test them like so:
beforeEach(function(){
var JSONResponse = {"master":[{"id":29,"fee_suite_id":8,"fee_parameter_id":1,"name":"American Express Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":30,"fee_suite_id":8,"fee_parameter_id":2,"name":"Discover Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":31,"fee_suite_id":8,"fee_parameter_id":3,"name":"MasterCard Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":32,"fee_suite_id":8,"fee_parameter_id":4,"name":"Visa Fee","multiplier":0.045,"addend":0.0,"order":1,"order_readonly":true,"created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_suites/8?association=fee_rules').respond(JSONResponse);
JSONResponse = {"master":[{"id":25,"fee_type_id":9,"name":"UPS Published Quote","value":"ups_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":26,"fee_type_id":9,"name":"FedEx Published Quote","value":"fedex_published_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":27,"fee_type_id":9,"name":"UPS Negotiated Quote","value":"ups_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"},{"id":28,"fee_type_id":9,"name":"FedEx Negotiated Quote","value":"fedex_negotiated_quote","parameter_type":"currency","created_at":"2016-10-17T14:20:08.000-05:00","updated_at":"2016-10-17T14:20:08.000-05:00"}]};
$httpBackend.expectGET('/api/v3/fee_types/9?association=fee_parameters').respond(JSONResponse);
$httpBackend.flush();
});
it('should set currentFeeRuleNum', function(){
expect(scope.FeeSuite.currentFeeRuleNum).toEqual(4);
});
When I run my test I get the following error:
Error: Unexpected request: GET /api/v3/fee_suites/
I know it is coming from root state's resolve function feeSuites because the test also prints to the console log the word 'here'.
I cannot figure out why it seems like the test doesn't stop and starts testing the RootCtrl in the root state. Could it have anything to do with the fact that state fee-rule is abstract? Also NewCtrl is defined but it is empty.
After some more googling with different keywords, turns out in my test I need to mock the $state variable inside FeeRuleCtrl. That fixed the problem.

Angular factory and controller

I am trying to learn about using factory with controller. I have seen one example with:
angular.module('flapperNews')
.factory('posts', ['$http', function($http) {
var o = {
posts: []
}
o.getPosts = function() {
return $http.get('api/posts').success(function(data) {
return data
})
};
o.create = function(post) {
return $http.post('api/posts', post).success(function(data) {
o.posts.push(data);
})
};
return o
}])
When I console.log(o.getPosts()), it returned the following:
Promise {$$state: Object}
$$state
:
Object
pending
:
undefined
processScheduled
:
false
status
:
1
value
:
Object
config
:
Object
data
:
Array[6]
0
:
Object
_id
:
"576d4904f2aa867dadb7b286"
link
:
"aaa"
title
:
"nice weather in Australia"
upvotes
:
0
__proto__
:
Object
__defineGetter__
:
__defineGetter__()
__defineSetter__
:
__defineSetter__()
__lookupGetter__
:
__lookupGetter__()
__lookupSetter__
:
__lookupSetter__()
constructor
:
Object()
hasOwnProperty
:
hasOwnProperty()
isPrototypeOf
:
The data I wanted is under Array[6] which is under $$state, does anyone have any idea what this is and how does one normally extract that data?
The data is supposed to be passed to my controller like so:
$stateProvider
.state('home', {
url: '/home',
templateUrl: 'views/posts.html',
controller: 'PostCtrl',
controllerAs: 'posts',
resolve: {
postPromise: ['posts', function(posts) {
console.log(posts.getPosts())
return posts.getPosts();
}]
}
});
Note:This is taken from an online tutorial. Would really appreciate it if someone can shed some light on this as I am new to factory etc. The current code doesn't return anything to my view, can you tell me where I have gone wrong?
Edited/Added: This is the implementation of controller. When I console.log(posts.posts) it returns an empty array []. Any ideas?
angular.module('flapperNews')
.controller('PostCtrl', [
'$scope','posts',
function($scope,posts){
$scope.posts=posts.posts;
$scope.incrementUpvotes=function(post){
post.upvotes+=1
}
$scope.addPost = function(){
if(!$scope.title || $scope.title === '') { return; }
posts.create({
title: $scope.title,
link: $scope.link,
});
$scope.title = '';
$scope.link = '';
};
}]);
How are you calling the factory's method in your controller? You are making an $http request which returns a promise.
You can learn about promises here:http://andyshora.com/promises-angularjs-explained-as-cartoon.html.
In short you can see promises as functions that execute right away but return data in the future (not right away). You will have to wait until the promise "resolves" to get the data. That is why is good to wrap any code that needs data from a promise, within the promise function itself.
In your controller you should call the factory method(getPosts()) like this:
posts.getPosts().then(function(response){
$scope.news = response.data; <---here is where you get your data for your news. You cant not declare **$scope.data** outside this promise function because you will miss the data.
});
Don't forget to inject your posts service/factory in your controller like this:
controller(['posts',function(posts){ ... });
You can also get the data in your route like this:
$stateProvider
.state('home',{
url:'/home',
templateUrl:'views/posts.html',
controller:'PostCtrl',
controllerAs:'posts',
resolve:{
postPromise: ['posts', function(posts){
return posts.getPosts().then(function(response){
return response.data
});
}]
}
})
Then in your controller you can inject the postPromise like this:
controller(['postPromise',function(postPromise){ ... });
Now you can assign the data to a variable in your controller like this:
$scope.news = postPromise;
Hope I answered your question. If I misunderstood please give more details or provide a codepen.

AngularJS Two way data binding

In my controller I have the following data:
(function() {
'use strict';
angular
.module('myappl.mymodule')
.controller('MyController', MyController);
MyController.$inject = ['$scope', 'myService'];
function MyController($scope, 'myService') {
$scope.vm = this;
var vm = this;
vm.myService = myService;
vm.userManagement = userManagement.data;
vm.userManagementSomeDataObjects = vm.userManagement.someDataObjects;
Somewhere in this controller I have a function which first gets data from backend and than invoke showModal:
function modalForUserInteraction() {
vm.myService.getData(parameters).success(function(data) {
vm.modalService.showModal(data, vm.userManagement, vm.userManagementSomeDataObjects);
}).error(function(data) {
console.log('error');
});
}
The modal- controller looks like this:
...
function showModalService($modal, $stateParams, otherService) {
var service = {
showModal: showModal
};
return service;
////////////
function showModal(data, userManagement, userManagementSomeDataObjects) {
var myModal = $modal.open({
controller: ModalController,
controllerAs: 'vm',
windowClass: "modal fade in",
resolve: {
userManagement: function() {
return userManagement;
},
userManagementSomeDataObjects: function() {
return userManagementSomeDataObjects;
}
},
templateUrl: 'url/to.html'
});
return myModal;
and in the modal controller there is a method like this one:
function ModalController(userManagement, userManagementSomeDataObjects) {
var vm = this;
...
function doSomeActionAfterButtonClickAtModal() {
otherService.getDataFromBackend(params).success(function(data) {
userManagement = data;
userManagementSomeDataObjects = data.someDataObjects;
})error(function(data) {
console.log('error');
});
}
If I do it like this:
userManagement = data; and userManagementSomeDataObjects = data.someDataObjects; than the new data is not set.
If I set each property separately of the objects than it works more often than not but somethimes it does not.
My question now would be what I can do in order to get it work.
Currently I do not have a $scope- variable in my modal and actually I don't know
if $scopeOfModal.$apply() would help and I also don't know how to get access from modal to MyController - $scope.
I would be glad for any hint in this direction.
Thanks a lot!
[EDIT]
Here is an image of my currently viewed (right) an on the left side the object, which should be shown after setting in modal- function.
[EDIT]
is there any posibility to pass parameters to this function in the modal controller:
this.previewArchivedSchedule = function(hereINeedParamerts) {
alert('archivedScheduleIntervalContainerId: ' + hereINeedParamerts);
};
This looks to me just like it may have nothing to do with angular, just some confusion with javascript variable references.
First you pass userManagement from showModalService.showModal to ModalController via resolve.
So now ModalController has a reference to the same object as showModalService.
However, in ModalController, you reassign the userManagement variable to point to data instead. So now the userManagement variable inside ModalController isn't pointing at the injected object anymore, because you've reassigned it. This has nothing to do with angular two-way data binding, it's just javascript variable assignment. You've lost your reference to the original object.
showModalService still has a reference to the instance that it sent in via resolve, it has no idea that you swapped the reference out in the ModalController.
I'd try sending over an object encapsulating the data you want to share to fix this problem.
function showModal(data, userManagement, userManagementSomeDataObjects) {
var myModal = $modal.open({
controller: ModalController,
controllerAs: 'vm',
windowClass: "modal fade in",
resolve: {
sharedData: function() {
return {
userManagement: userManagement,
userManagementSomeDataObjects: userManagementSomeDataObjects
}
},
templateUrl: 'url/to.html'
});
return myModal;
Then manipulate the properties on the shared object instead of overwriting references.
function ModalController(sharedData) {
var vm = this;
...
function doSomeActionAfterButtonClickAtModal() {
otherService.getDataFromBackend(params).success(function(data) {
sharedData.userManagement = data;
sharedData.userManagementSomeDataObjects = data.someDataObjects;
})error(function(data) {
console.log('error');
});
}

How to create an object with properties in the resolve parameter in the route provider in Angular?

I want to pre-load data in my controller. I am doing this using resolve in the routeprovider:
.when('/customers', {
controller: 'CustomerController', templateUrl: '/Customer/Index', resolve: {
countries: CustomerController.loadCountries,
genders: CustomerController.loadGenders,
}
})
As you can see I have two objects which will be injected into my controller, countries and gender. All this works fine.
What I want to do is, I want those objects to be part of one object: listData. I've tried:
.when('/customers', {
controller: 'CustomerController', templateUrl: '/Customer/Index', resolve: {
listData: {
countries: CustomerController.loadCountries,
genders: CustomerController.loadGenders
}
}
})
but this doesn't work: Argument 'fn' is not a function, got Object.
What is the right syntax / approach to accomplish this?
If you pass an object to a key, you must have a function as the value :
listData: function () {
// you need to inject
var deferred = $q.defer();
var listData = {};
// these should be services, btw !
CustomerController.loadCountries.then(function (countries) {
listData.countries = countries;
// resolve when you have both
if (listData.genders) deferred.resolve(listData);
});
CustomerController.loadGenders.then(function (genders) {
listData.genders = genders;
if (listData.countries) deferred.resolve(listData);
});
return deferred.promise;
}

Resources