Is it the recommend way to wrap providers/modules in factorys? - angularjs

For example I use
https://github.com/alexcrack/angular-ui-notification
It is recommend / cleaner to wrap such modules in seperate factorys?
Thanks!
Here is a example with toastr where its wrapped in a factory:
app.factory('notificationFactory', function () {
return {
success: function (text) {
toastr.success(text,"Success");
},
error: function (text) {
toastr.error(text, "Error");
}
};
});
Are there some benefits instead of using it directly in the controller:
toastr.success("Everything fine!","Success");

Related

Add angularjs component via service

I am trying to add angularjs component using the code as per below. app.component doesn't work like this
Where as if I execute app.component from outsite it works.
How to fix this issue. Notice that the API will return component names. I just need to render them.
app.service('applookup', function($http) {
this.register = function() {
return $http.get('http://localhost:3003/api/provider/fetch/app1').then(function(res){
app.component('appleComponent', {
template : 'test'
});
return res.data.componentList;
});
}
});
As #William and #Zooly mentioned in comments. We can add reference to $compileProvider.component as per below in app.config
app.register = {
component : function(name, object) {
$compileProvider.component(name, object);
return (this);
}
Then use app.register.component to register the component

Unit Test Resolve Block in Angular component

Does anyone know how to unit test resolve block items in a routed component? It would be great if anyone can test courseDetails in the resolve block as an example.
(function() {
'use strict';
angular
.module('writingsolutionsComponents')
.component('courseSettings', {
bindings: {
newCourseForm: '<',
courseDetails: '<',
timezones: '<',
citations: '<',
disciplines: '<'
},
templateUrl: 'course-settings/course-settings.html',
controller: 'CourseSettingsController'
})
.config(stateConfig);
stateConfig.$inject = ['$stateProvider'];
function stateConfig($stateProvider, $urlRouterProvider) {
$stateProvider.state('course-settings', {
parent: 'app',
url: '/:courseId/course-settings',
data: {
pageTitle: "Hello World"
},
views: {
'content#': {
component: 'courseSettings'
}
},
resolve: {
courseDetails: function(CourseService, $stateParams) {
return CourseService.get($stateParams.courseId);
},
timezones: function(TimezoneService) {
return TimezoneService.getTimeZones();
},
citations: function(CitationService) {
return CitationService.getCitations();
},
disciplines: function(DisciplineService) {
return DisciplineService.getAllDisciplines();
}
}
}
});
}
})();
I tried following and it didn't workout for me.
CourseServiceMock = {
get: function () {
return $q.resolve({courseId: "32432535", title: "Communication"});
}
};
spyOn(CourseServiceMock , 'get').and.callThrough();
expect(CourseServiceMock .get).toHaveBeenCalled()
Do not test your framework(s). Here that means do not test ui-router, it is known to work correctly. Specifically, we know it is going to call the registered resolve functions.
If you wish to test the resolve function itself, it is actually quite simple. We just need to make the function available to test. We can actually test this function without launching a web browser or doing anything fancy.
Here is an example that uses a test library called blue-tape* to run the test using NodeJS, you can adapt it to use Jasmine if you must.
// tests.js
const test = require("blue-tape");
test("courseDetails resolve retrieves the course based on route/:courseId", async t => {
// arrange
const courses = [{courseId: "32432535", title: "Communication"}];
const mockCourseService = {
get(id) {
return Promise.resolve(courses.find(course => course.id === id));
}
};
const mock$stateParams = {courseId: "32432535"};
// act
const result = await courseDetails(mockCourseService, mock$stateParams);
// assert
t.equal(result.courseId, courses[0].courseId);
});
To get this working, run
npm install --save-dev blue-tape
And then run
node test.js
The benefit is that your test does not depend on ui-router or even AngularJS and that it just tests what you care about, your business logic.
Note, this test itself is a bit arbitrary.
*I am using blue-tape instead of tape here because it makes testing asynchronous functions very simple as it automatically handles any function that returns a thennable.

How to pass a variable to javascript in nested function

I have several protractor / angularjs it blocks that repeat the same bit of code that I would like to put inside a function. I want to just call the function instead of repeating this over and over.
it('should move directly to Draft', function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(function(elem) {
return elem.getText().then(function(text) {
return text === 'Draft';
});
}).click();
});
This part of the block is the repeated part I want to create a function for. I am new to javascript so this is eluding me on how to do this.
return elem.getText().then(function(text) {
return text === 'Draft';
});
}).click();
I need to be able to substitute 'Draft' with different variables. I am using page objects for part of this and I am not sure A) how to create a function like this and pass in my text & B) if it should go on the spec side or the page side? This is probably pretty basic for most folks. But since I am new to javascript I am having trouble wrapping my head around this.
I would extract the whole filter function into a "helpers" module.
helpers.js:
var Helpers = function () {
this.filterByText = function (text) {
return function (elem) {
return elem.getText().then(function(actualText) {
return actualText === text;
});
};
}
}
module.exports = new Helpers();
Usage in the test:
var helpers = require("helpers");
describe("My Test", function () {
it('should move directly to Draft', function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(helpers.filterByText('Draft')).click();
});
});
Maybe something like this?
describe('...something...', function()
{
var clickBtn;
beforeEach(function()
{
clickBtn = function(testText)
{
return posting_action_page.move_action.filter(function(elem)
{
return elem.getText().then(function(currentText)
{
return currentText === testText;
});
}).click();
};
});
it('should move directly to Draft', function()
{
posting_sum_page.locate_action_button.click();
expect(clickBtn('Draft')).toEqual('...something...');
});
});
If you want to reuse the return block only
it('should move directly to' + targetText, function() {
posting_sum_page.locate_action_button.click();
posting_action_page.move_action.filter(function(elem) {
checkSameText(elem, targetText);
}).click();
});
function checkSameText(el, targetText) {
return el.getText().then(function(text) {
return text === targetText;
});
}
I'm unsure what the page object type is for posting_action_page.move_action but what I think you are looking for is using by.buttonText or by.linkText.
// html: <button>Draft</button>
element(by.buttonText('Draft')).click();
// html: <a href="">Draft</button>
element(by.linkText('Draft')).click();
There are other locators that might be helpful like by.partialButtonText and by.partialLinkText.

Implementing component require property in Angular 1.5 components

I am having no joy with implementing require: {} property as part of an angular component. Allow me to demonstrate with an example I have.
This is the component/directive that supposed to fetch a list of judgements. Nothing very fancy, just a simple factory call.
// judgements.component.js
function JudgementsController(GetJudgements) {
var ctrl = this;
ctrl.Get = function () {
GetJudgements.get().$promise.then(
function (data) {
ctrl.Judgements = data.judgements;
}, function (error) {
// show error message
});
}
ctrl.$onInit = function () {
ctrl.Get();
};
}
angular
.module('App')
//.component('cJudgements', {
// controller: JudgementsController,
//});
.directive('cJudgements', function () {
return {
scope: true,
controller: 'JudgementsController',
//bindToController: true,
};
});
I am trying to implement component require property to give me access to ctrl.Judgements from the above component/directive as follows:
// list.component.js
function ListController(GetList, GetJudgements) {
var ctrl = this;
ctrl.list = [];
ctrl.Get = function () {
GetList.get().$promise.then(
function (data) {
ctrl.list = data.list;
}, function (error) {
// show error message
});
};
//ctrl.GetJudgements = function () {
// GetJudgements.get().$promise.then(
// function (data) {
// ctrl.Judgements = data.judgements;
// }, function (error) {
// // show error message
// });
//}
ctrl.$onInit = function () {
ctrl.Get();
//ctrl.GetJudgements();
};
}
angular
.module('App')
.component('cTheList', {
bindings: {
listid: '<',
},
controller: ListController,
controllerAs: 'ctrl',
require: {
jCtrl: 'cJudgements',
},
template: `
<c-list-item ng-repeat="item in ctrl.list"
item="item"
judgements="ctrl.Judgements"></c-list-item>
<!--
obviously the reference to judgements here needs to change
or even better to be moved into require of cListItem component
-->
`,
});
Nice and simple no magic involved. A keen reader probably noticed GetJudgement service call in the ListController. This is what I am trying to remove from TheList component using require property.
The reason? Is actually simple. I want to stop database being hammered by Judgement requests as much as possible. It's a static list and there is really no need to request it more than once per instance of the app.
So far I have only been successful with receiving the following error message:
Error: $compile:ctreq
Missing Required Controller
Controller 'cJudgements', required by directive 'cTheList', can't be found!
Can anyone see what I am doing wrong?
PS: I am using angular 1.5
PSS: I do not mind which way cJudgement is implemented (directive or component).
PSSS: If someone wonders I have tried using jCtrl: '^cJudgements'.
PSSSS: And multiple ^s for that matter just in case.
Edit
#Kindzoku posted a link to the article that I have read before posting the question. I hope this also helps someone in understanding $onInit and require in Angular 1.5+.
Plunker
Due to popular demand I made a plunker example.
You should use required components in this.$onInit = function(){}
Here is a good article https://toddmotto.com/on-init-require-object-syntax-angular-component/
The $onInit in your case should be written like this:
ctrl.$onInit = function () {
ctrl.jCtrl.Get();
};
#iiminov has the right answer. No parent HTML c-judgements was defined.
Working plunker.

AngularJS - What would this look like had it been created TDD style?

I'm in the process of transferring all of our code onto Karma and Jasmine and am having a hard time figuring out where I start.
What would this code look like had I started building it from a TDD standpoint? What does a simple test look like?
Note: This code works 100%, but I don't have any tests setup.
(function() {
"use strict";
angular.module('system_centers', [
'system'
])
.factory('System', ['Api', function(Api) {
this.loadSystem = function(contactId, cardId) {
return Api.get('lmc/contact/system/' + contactId, {
card_id: cardId
});
};
this.completeSystem = function(recordId) {
return Api.put('system/complete/' + recordId);
};
this.createSystem = function(contactId, cardId) {
if (+contactId === 0 || +cardId === 0) {
return false;
}
return Api.post('contact/system/' + contactId, {
card_id: cardId,
type: 'systems',
origin: 'lmc'
});
};
return this;
}])
.controller('System_centersCtrl', ['$scope', 'System', function($scope, System) {
$scope.main.cardType = 'systems';
$scope.main.type = 'system_centers';
$scope.completeSystem = function(recordId) {
System.completeSystem(recordId).success(function(){
toastr.success("System completed!");
$scope.createSystem();
$scope.loadSystems();
});
};
$scope.createSystem = function() {
System.createSystem($scope.main.contactId, $scope.main.cardId).success(function() {
$scope.loadSystem($scope.main.contactId, $scope.main.cardId);
$scope.loadContacts();
});
};
$scope.loadSystem = function() {
System.loadSystem($scope.main.contactId, $scope.main.cardId).success(function(data) {
if (data.error) {
$scope.createSystem();
} else {
$scope.main.record = data.record;
}
});
};
$scope.loadSystems();
}]);
})();
Testing is easy, you just need to assert that your factory is working correctly. This doesn't mean that you want actually get/put/post stuff, that belongs to the Api test. Here we just want to know that calling certain functions of our factory will call some Api functions with the correct parameters.
I imagine that Api belongs to the system module. I load it and mock it:
beforeEach(module('system', function($provide) {
api = {
get: function(url, params) {},
put: function(url, params) {},
post: function(url, params) {}
};
spyOn(api, 'get');
spyOn(api, 'put');
spyOn(api, 'post');
$provide.value('Api', api);
}));
module will load your system module and then we just need to create a simple object with the interface of our Api service. No need to implement anything on them.
Then we just need to spy the methods (to be able to assert that they have been called).
Next, we load the system_centers module and we inject our services:
beforeEach(module('system_centers'));
beforeEach(inject(function(System) {
system = System;
}));
inject is used to inject dependencies in our tests. We just need to inject our System factory.
What rest are the test, I created a bunch of them:
it('should load the system', function() {
system.loadSystem(1, 0);
expect(api.get).toHaveBeenCalledWith('lmc/contact/system/1', {card_id : 0});
});
it('should be able to complete the system', function() {
system.completeSystem(20);
expect(api.put).toHaveBeenCalledWith('system/complete/20');
});
it('should create the system', function() {
system.createSystem(1, 3);
expect(api.post).toHaveBeenCalledWith('contact/system/1', { card_id: 3, type: 'systems', origin: 'lmc'});
});
it('should not create the system if contact_id is 0', function() {
system.createSystem(0, 20);
expect(api.post).not.toHaveBeenCalled();
});
it('should not create the system if card_id is 0', function() {
system.createSystem(1, 0);
expect(api.post).not.toHaveBeenCalled();
});
They are much the same. We call some factory method and we expect that our Api has been called with some parameters. Or even that calling createSystem with contact or card id with 0 won't call the Api.
Well, this is a good head start. You can continue with more tests or with other parts of your application.
Here is the plunker: http://plnkr.co/edit/5vfg0Y1G0vo2nnz0xByN?p=preview

Resources