I am building a demo app. I want this AngularJS app to have factory as well.
I keep getting error: "SyntaxError: function statement requires a name"
Below is my code:
var bookApp = angular.module('bookAppModule',[]);
bookApp.controller('boookbAppCtrl', ['$scope','$http',Book ,
function($scope,$http,Book) {
$scope.way=["Normal","$http","RestFul"];
$scope.books =
[
{"title":"abc","author":"zxc"},
{"title":"def","author":"cvb"},
{"title":"ghi","author":"nml"},
{"title":"jkl","author":"kjh"},
{"title":"mno","author":"fds"}
];
var names=["Anuj","Donvir"];
$scope.newbooks = Book.getBooks;
}]);
bookApp.factory('Book',
function(){
getBooks : function(){
return
[
{"title":"newbook1","author":"zxc"},
{"title":"newbook2","author":"cvb"},
{"title":"newbook3","author":"nml"},
{"title":"newbook4","author":"kjh"},
{"title":"newbook5","author":"fds"}
];
}
});
In your factory, you forgot to write the overall return function that returns all the 'methods' in the service.
bookApp.factory('Book',
function(){
return { // you did not have this return, only its body
getBooks : function(){
return
[
{"title":"newbook1","author":"zxc"},
{"title":"newbook2","author":"cvb"},
{"title":"newbook3","author":"nml"},
{"title":"newbook4","author":"kjh"},
{"title":"newbook5","author":"fds"}
];
}
}
});
Addition
Also, to add on the above reason which caused the error, personally i battled for many days with a similar error in angular services. It was caused by the overall return function not being in the same line with the { that contains the body of the return. See below.
// will cause the error
bookApp.factory('Book',
function(){
return
{ // { below not on same line with return
getBooks : function(){
// ...
}
}
});
// will not cause the error
bookApp.factory('Book',
function(){
return { // { on same line with return
getBooks : function(){
// ...
}
}
});
I do not know the exact reason for this behaviour but what i know is that when you use them on same line, it will work and you will not have to stall your project like i did many times.
If you need to forget the position of the braces, you can define this angular factory using Revealing Module Pattern as like this...
bookApp.factory('Book',
function(){
var factoryServices=
{
getBooks: getBooks
};
return factoryServices;
function getBooks()
{
// ...
}
}
});
For more reading: https://github.com/johnpapa/angular-styleguide#style-y052
Related
I have a problem when I try to log some data inside the function of webtorrent.
I want to log some values of this.client.add but I don't have access.
Some idea of what's going on here?
import Webtorrent from 'webtorrent';
class PlaylistController {
/** #ngInject */
constructor($http, $log) {
this.log = $log;
this.client = new Webtorrent();
$http
.get('app/playlist/playlist.json')
.then(response => {
this.Torrent = response.data;
});
}
addTorrent(magnetUri) {
this.log.log(magnetUri);
this.client.add(magnetUri, function (torrent) {
// Got torrent metadata!
this.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
this.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}
}
export const playlist = {
templateUrl: "app/playlist/playlist.html",
controller: PlaylistController,
bindings: {
playlist: '<'
}
};
Another thing its I use yeoman for the scaffold of my app and its has JSLint with console.log forbidden and its said that you must use angular.$log, but the thing its I don't wanna change that, I wanna understand the problem here.
You either need to refer to this (the class) as another variable to use inside the function(torrent) function or use arrow functions so that this reference remains the class one.
Solution 1, using another variable to ref the class:
addTorrent(magnetUri) {
this.log.log(magnetUri);
var that = this;
this.client.add(magnetUri, function (torrent) {
// Got torrent metadata!
that.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
that.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}
Solution 2, using arrow functions:
addTorrent(magnetUri) {
this.log.log(magnetUri);
this.client.add(magnetUri, torrent => {
// Got torrent metadata!
this.log.log('Client is downloading:', torrent.infoHash);
torrent.files.forEach(file => {
this.log(file);
});
});
this.log.log('sda');
this.log.log(this.client);
}
In previous questions I have seen that a nice way to wait for the url to change is to use:
browser.wait( function() {
return browser.getCurrentUrl().then(function(url) {
return /myURL/.test(url);
});
}, 10000, "url has not changed");`
But I am trying to have a method that I can pass myURL as a variable (in case I need to use it with other sites) and is not working.
I am trying this in my Page Object file:
this.waitUrl = function(myUrl) {
browser.wait( function(myUrl) {
return browser.getCurrentUrl().then(function(url, myUrl) {
return myUrl.test(url);
});
}, 10000, "url has not changed");
};
Any ideas if this is even possible and how to do it if so?
Update (July 2016): with Protractor 4.0.0 you can solve it with urlIs and urlContains built-in Expected Conditions.
Original answer:
Don't pass myUrl inside the then function, it is available from the page object function scope:
browser.wait(function() {
return browser.getCurrentUrl().then(function(url) {
return myUrl.test(url);
});
}, 10000, "url has not changed");
I would though define it as an Expected Condition:
function waitUrl (myUrl) {
return function () {
return browser.getCurrentUrl().then(function(url) {
return myUrl.test(url);
});
}
}
So that you can then use it this way:
browser.wait(waitUrl(/my\.url/), 5000);
For those that want an example for Protractor 4.0.0 through 5.3.0
You can use "ExpectedConditions" like so...
var expectedCondition = protractor.ExpectedConditions;
// Waits for the URL to contain 'login page'.
browser.wait(expectedCondition.urlContains('app/pages/login'), 5000);
If you want to validate this with an e2e test.
it('should go to login page', function() {
loginPage.login();
const EC = protractor.ExpectedConditions;
browser.wait(EC.urlContains('app/pages/login'), 5000).then(function(result) {
expect(result).toEqual(true);
});
});
(I asked this question recently and accepted an answer but it's still not what I need.) I really need to create dynamic tests from data loaded from a module. Each item from the array will have it's own describe statement with certain protractor actions. My previous post has an answer that says to use an it statement, but I can't do that because there's too much going on.
My main problem is that the data doesn't get loaded in time for the describe. I had another suggestion to use VCR.js or something similar but I don't think those will work because I'm using a module. Is there a way I can save the data to a separate file and load it in? Would that be a good way to go?
var data = require('get-data'); //custom module here
describe('Test', function() {
var itemsArr;
beforeAll(function(done) {
data.get(function(err, result) {
itemsArr = result; //load data from module
done();
});
})
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
UPDATE:
I used Eugene's answer and came up with this. I can't test each individual study how I want because the it statement doesn't fire. Is this problem even solvable??
describe('check each item', function () {
it('should load data', function (done) {
browser.wait(itemsPromise, 5000);
itemsPromise.then(function(itemsArr) {
expect(itemsArr).toBeTruthy();
studyArr = itemsArr.filter(function (item) {
return item.enabled && _.contains(item.tags, 'study');
});
studyCount = studyArr.length;
expect(studies.count()).toEqual(studyCount);
checkItems(studyArr);
done();
});
});
function checkItems (itemsArr) {
itemsArr.forEach(function (item) {
describe(item.id, function () {
console.log('checkItems', item.id);
// doesn't work
it('should work', function (done) {
expect(false).toBeTruthy();
done();
});
});
});
}
});
You're trying to do something that Jasmine does not allow: generating tests after the test suite has started. See this comment on an issue of Jasmine:
Jasmine doesn't support adding specs once the suite has started running. Usually, when I've needed to do this, I've been able to know the list of options ahead of time and just loop through them to make the it calls. [...]
("adding specs" === "adding tests")
The point is that you can generate tests dynamically but only before the test suite has started executing tests. One corollary of this is that the test generation cannot be asynchronous.
Your second attempt does not work because it is trying to add tests to a suite that is already running.
Your first attempt is closer to what you need but it does not work either because describe calls its callback immediately, so beforeAll has not run by the time your describe tries to generate the tests.
Solutions
It all boils down to computing the value of itemsArr before the test suite start executing tests.
You could create a .getSync method that would return results synchronously. Your code would then be something like:
var data = require('get-data'); //custom module here
var itemsArr = data.getSync();
describe('Test', function() {
describe('check each item', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
[...]
If writing .getSync function is not possible, you could have an external process be responsible for producing a JSON output that you could then deserialize into itemsArr. You'd execute this external process with one of the ...Sync functions of child_process.
Here's an example of how the 2nd option could work. I've created a get-data.js file with the following code which uses setTimeout to simulate an asynchronous operation:
var Promise = require("bluebird"); // Bluebird is a promise library.
var get = exports.get = function () {
return new Promise(function (resolve, reject) {
var itemsArr = [
{
name: "one",
param: "2"
},
{
name: "two",
param: "2"
}
];
setTimeout(function () {
resolve(itemsArr);
}, 1000);
});
};
// This is what we run when were are running this module as a "script" instead
// of a "module".
function main() {
get().then(function (itemsArr) {
console.log(JSON.stringify(itemsArr));
});
};
// Check whether we are a script or a module...
if (require.main === module) {
main();
}
Then, inside the spec file:
var child_process = require('child_process');
var itemsArr = JSON.parse(child_process.execFileSync(
"/usr/bin/node", ["get-data.js"]));
describe('Test', function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
I've tested the code above using jasmine-node. And the following file structure:
.
├── data.js
├── get-data.js
└── test
└── foo.spec.js
./node_modules has bluebird and jasmine-node in it. This is what I get:
$ ./node_modules/.bin/jasmine-node --verbose test
describe
describe
it
it
Test - 5 ms
one - 4 ms
should work - 4 ms
two - 1 ms
should work - 1 ms
Finished in 0.007 seconds
2 tests, 2 assertions, 0 failures, 0 skipped
Try to use a promise, something like:
var deferred = protractor.promise.defer();
var itemsPromise = deferred.promise;
beforeAll(function() {
data.get(function(err, result) {
deferred.fulfill(result);
});
})
And then:
describe('check each item', function() {
itemsPromise.then(function(itemsArr) {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
Another solution I can think of is to use browser.wait to wait until itemsArr becomes not empty.
Is your get-data module doing some browser things with protractor? If so, you will need to set/get itemsArr within the context of the controlFlow. Otherwise it will read all the code in the get-data module, but defer its execution and not wait for it to finish before moving right along to those expect statements.
var data = require('get-data'); //custom module here
var itemsArr;
describe('Test', function() {
beforeAll(function() {
// hook into the controlFlow and set the value of the variable
browser.controlFlow().execute(function() {
data.get(function(err, result) {
itemsArr = result; //load data from module
});
});
});
//error: Cannot read property 'forEach' of undefined
describe('check each item', function() {
// hook into the controlFlow and get the value of the variable (at that point in time)
browser.controlFlow().execute(function() {
itemsArr.forEach(function(item) {
checkItem(item);
});
});
});
function checkItem (item) {
var itemName = item.name;
describe(itemName, function() {
console.log('describe');
it('should work', function() {
console.log('it');
expect(true).toBeTruthy();
});
});
}
});
I have 2 arrays, sports and leagues and I want those arrays to become resolve before anything on the page, I will paste all of my code regarding those arrays I just mentioned. I need to do this due to an issue I am having with the Angular filters.
this is my html
<div ng-repeat="sport in sportsFilter = (sports | filter:query)">
<div ng-if="sport.leagues.length">
<!--first array-->
{{sport.name}}
</div>
<div ng-repeat="league in sport.leagues">
<!--second array-->
{{league.name}}
</div>
</div>
controller
.controller('SportsController', function($scope, $state, AuthFactory,
SportsFactory, Sports) {
$scope.sports = [];
$scope.sportPromise = Sports;
AuthFactory.getCustomer().then(function(customer) {
$scope.customer = customer;
SportsFactory.getSportsWithLeagues(customer).then(function(sports) {
$ionicLoading.hide();
if (sports.length) {
$scope.sportPromise = Sports;
$scope.sports = sports;
}else {
AuthFactory.logout();
}
}, function(err) {
console.log(err);
});
}
});
$scope.isSportShown = function(sport) {
return $scope.shownSport === sport;
};
});
and here de app.js so far, I thought with this I was resolving the arrays, actually the must important is the array named leagues, but still is giving me troubles
.state('app.sports', {
url:'/sports',
views:{
menuContent:{
templateUrl:'templates/sportsList.html',
controller:'SportsController',
resolve: {
Sports: function(SportsFactory, AuthFactory, $q) {
var defer = $q.defer();
AuthFactory.getCustomer().then(function(customer) {
SportsFactory.getSportsWithLeagues(customer).then(function(sports) {
var sportLeagues = _.pluck(sports, 'leagues'),
leaguesProperties = _.chain(sportLeagues).flatten().pluck('name').value();
console.log(leaguesProperties);
defer.resolve(leaguesProperties);
});
});
return defer.promise;
}
}
}
}
})
UPDATE:
the page is loading and I my filter is getting the array leagues empty, so is not searching thru to it, so I need that array to load first than the filters.
Here's how this could work at a high-level, including avoiding some mistakes you are making.
Mistakes:
You don't need to use $q.defer when the API you are using is already returning a promise. Just return that promise. What you are doing is called an deferred anti-pattern.
resolve is used when you need to do something (like authentication) before you are hitting a particular state. You are under-using the resolve by not resolving the customer, and instead doing this in the controller.
resolve property of $stateProvider can accept other resolves as parameters.
With these out of the way, here's how it could work:
.state('app.sports', {
resolve: {
customer: function(AuthFactory){
return AuthFactory.getCustomer();
},
sports: function(SportsFactory, customer){
return SportsFactory.getSportsWithLeagues(customer);
},
leagues: function(sports){
var leagueProperties;
// obtain leagueProperties from sports - whatever you do there.
return leagueProperties;
}
}
});
Then, in the controller you no longer need AuthFactory - you already have customer:
.controller('SportsController', function($scope, customer, sports, leagues){
$scope.sports = sports;
})
As per request in the comments of New Dev's answer the same only using array notation so that the code remains minifiable:
.state('app.sports', {
resolve: {
customer: ['AuthFactory', function(AuthFactory){
return AuthFactory.getCustomer();
}],
sports: ['SportsFactory', 'customer', function(SportsFactory, customer){
return SportsFactory.getSportsWithLeagues(customer);
}],
leagues: ['sports', function(sports){
var leagueProperties;
// obtain leagueProperties from sports - whatever you do there.
return leagueProperties;
}]
}
});
A little explanation to go with that, when you minify this:
function (AuthFactory) {
return AuthFactory.getCustomer();
}
You get something like this:
function (_1) {
return _1.getCustomer();
}
Now it will try to inject _1 which is not defined so the code will fail. Now when you minify this:
['AuthFactory', function(AuthFactory){
return AuthFactory.getCustomer();
}]
You'll get this:
['AuthFactory', function(_1){
return _1.getCustomer();
}]
And that will keep working because now _1 is assigned to AuthFactory because angular injects the first parameter in the function with the first string in the array.
Reference: https://docs.angularjs.org/guide/di (see: inline array notation)
I am trying to add an WatchList feature in the existing code discourse ember rails application
I have addded the following code
Discourse.Route.buildRoutes(function() {
var router = this;
this.resource('watchLists', { path: '/watch_lists' }, function() {
this.resource('watchList', {path: ':watch_list_id'});
});
});
In the ember Controller
Discourse.WatchListsController = Discourse.ObjectController.extend({});
In the ember model
Discourse.WatchList = Discourse.Model.extend({});
Discourse.WatchList.reopenClass({
find: function() {
jQuery.getJSON("watch_lists").then(function(json) {
var watch_lists = json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
});
});
In the ember view js
Discourse.WatchListsView = Ember.View.extend({});
In ember route js
Discourse.WatchListsRoute = Discourse.Route.extend({
model: function() {
return Discourse.WatchList.find();
}
});
When i renderring the handlebars template I am getting an WatchListsController object withot the data we have got from the ajax.
Can any body point out where i am doing wrong.
I see two possible problems.
First, you probably want WatchListsController to extend Discourse.ArrayController, not Discourse.ObjectController.
Second your reopen block is not valid JavaScript in the example code that you posted. I count four { but only two }. You probably want something kind of like this:
Discourse.WatchList.reopenClass({
find: function() {
return jQuery.getJSON("watch_lists").then(function(json) {
return json.watch_lists.map(function(attrs) {
return Discourse.WatchList.create(attrs);
}
});
}
});