I am trying to get a angular modal form working but I am always getting an unknown provider error. I think I have included all the necessary files?
Here is my code for calling the service:
function deleteConfirm() {
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Delete Supplier',
headerText: 'Delete ' + supplierName + '?',
bodyText: 'Are you sure you want to delete this supplier?'
};
modalService.showModal({}, modalOptions).then(function(result) {
if (result === 'ok') {
alert("ok");
}
}, function(error) {
alert("Error deleting");
});
}
And here is the code for the service:
(function() {
'use strict';
modalService.$inject = '$uibModal';
angular.module('plunker').factory('modalService', modalService);
function modalService($uibModal) {
var injectParams = ['$uibModal'];
//var modalService = function($uibModal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl: 'modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function(customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function(customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in this service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in this service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function($scope, $uibModalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function(result) {
$uibModalInstance.close('ok');
};
$scope.modalOptions.close = function(result) {
$uibModalInstance.close('cancel');
};
};
tempModalDefaults.controller.$inject = ['$scope', '$uibModalInstance'];
}
return $uibModal.open(tempModalDefaults).result;
};
}
}());
http://plnkr.co/edit/xNpbI42UJm8acODSOimR
Thanks for any help
angular.module('plunker').factory('modalService', modalService);
modalService.$inject = ['$uibModal'];
Try this!
Add to main app.js dependencies ['ui.bootstrap']
var app = angular.module('plunker', ['ui.bootstrap']);
You forgot to include "ui.bootstrap" into your app.
Simply bootstrap your app like this to correct the issue :
var app = angular.module('plunker', ["ui.bootstrap"]);
Related
this is my controller:
angular
.module('studentsApp')
.controller('StudentsController', StudentsController);
function StudentsController($scope, StudentsFactory) {
$scope.students = [];
$scope.specificStudent= {};
var getStudents = function() {
StudentsFactory.getStudents().then(function(response) {
if($scope.students.length > 0){
$scope.students = [];
}
$scope.students.push(response.data);
});
};
}
This is my factory:
angular.module('studentsApp')
.factory('StudentsFactory', function($http) {
var base_url = 'http://localhost:3000';
var studentsURI = '/students';
var studentURI = '/student';
var config = {
headers: {
'Content-Type': 'application/json'
}
};
return {
getStudents: function() {
return $http.get(base_url + studentsURI);
}
};
});
And here is how I'm trying to unit test the controller:
describe('Controller: Students', function() {
var StudentsController, scope, StudentsFactory;
beforeEach(function() {
module('studentsApp');
inject(function($rootScope, $controller, $httpBackend, $injector) {
scope = $rootScope.$new();
httpBackend = $injector.get('$httpBackend');
StudentsFactory = $injector.get('StudentsFactory');
StudentsController = $controller('StudentsController', {
$scope : scope,
'StudentsFactory' : StudentsFactory
});
students = [{
name: 'Pedro',
age: 10
}, {
name: 'João',
age: 11
}, {
name: 'Thiago',
age: 9
}];
spyOn(StudentsFactory, 'getStudents').and.returnValue(students);
});
});
it('Should get all students', function() {
scope.students = [];
StudentsController.getStudents();
$scope.$apply();
expect(scope.students.length).toBe(3);
});
});
The problem is when I run the test, the following message is displayed:
undefined is not a constructor (evaluating
'StudentsController.getStudents()')
I looked at the whole internet trying to find a tutorial that can help me on that, but I didn't find anything, could someone help me here?
It's link to the fact that the function getStudent() is private (declared by var). Thus your test can't access it. You have to attach it to the $scope or this to be able to test it.
I generally use this in controller:
var $this = this;
$this.getStudents = function() {
...
};
There's no StudentsController.getStudents method. It should be
this.getStudents = function () { ... };
Mocked StudentsFactory.getStudents returns a plain object, while it is expected to return a promise.
$controller shouldn't be provided with real StudentsFactory service as local dependency (it is already provided with it by default):
var mockedStudentsFactory = {
getStudents: jasmine.createSpy().and.returnValue($q.resolve(students))
};
StudentsController = $controller('StudentsController', {
$scope : scope,
StudentsFactory : mockedStudentsFactory
});
I'm trying to setup a restful API interface via AngularJS with the following code:
'use strict';
(function(angular) {
function ApiAction($resource, ResourceParameters) {
return $resource(ResourceParameters.route,
{ },
{ api_index: {
method: ResourceParameters.method,
isArray: true
}
});
return $resource(ResourceParameters.route,
{ },
{ create: {
method: ResourceParameters.method,
isArray: true
}
}
);
}
function ResourceParameters($scope) {
var factory = {};
factory.method = '';
factory.route = '';
factory.SetMethod = function(method) {
factory.method = method;
}
factory.SetRoute = function(route) {
factory.route = route;
}
return factory;
}
function superheroCtr($scope, ApiAction, ResourceParameters) {
$scope.superheroSubmit = function() {
// ApiAction.create({}, { superhero_name: $scope.superheroName, age: $scope.superheroAge });
angular.forEach($scope.superheroes, function(hero) {
// ApiAction.create({}, { superhero_name: hero.superhero_name, age: hero.age });
});
};
var heroesResources = ResourceParameters($scope).SetRoute('/api/');
var heroes = ApiAction.api_index({}, heroesResources);
$scope.superheroes = [];
heroes.$promise.then(function(data) {
angular.forEach(data, function(item) {
$scope.superheroes.push(item);
});
}, function(data) {
//if error then...
});
$scope.appendSuperheroFields = function() {
var i = $scope.superheroes.length + 1;
$scope.superheroes.push({"id": i, age: "", superhero_name: "" })
}
}
var superheroApp = angular.module('superheroApp', ['ngResource']);
superheroApp.controller('superheroCtr', ['$scope', 'ApiAction', 'ResourceParameters', superheroCtr]);
superheroApp.factory('ResourceParameters', ['$scope', ResourceParameters]);
superheroApp.factory('ApiAction', ['$resource', ResourceParameters, ApiAction]);
})(angular);
Yet, when I run it I get the following error:
Error: [$injector:itkn] Incorrect injection token! Expected service name as string, got function ResourceParameters($scope)
Why is this?
Simply you can not inject $scope OR you can not have access to $scope
inside a factory
Your problem is at this line
superheroApp.factory('ResourceParameters', ['$scope', ResourceParameters]);
You need to replace that line with
superheroApp.factory('ResourceParameters', [ResourceParameters]);
Factory
function ResourceParameters() { //<--removed $scope from here
var factory = {};
factory.method = '';
factory.route = '';
factory.SetMethod = function(method) {
factory.method = method;
}
factory.SetRoute = function(route) {
factory.route = route;
}
return factory;
}
Update
Additionally you should correct the declaration of ApiAction where ResourceParameters should be placed inside ' single qoutes
superheroApp.factory('ApiAction', ['$resource', 'ResourceParameters', ApiAction]);
I have the following code for a directive using a separated controller with the "controller as" syntax:
'use strict';
angular.module('directives.featuredTable', [])
.controller('FeaturedTableCtrl',
['$scope',
function ($scope){
var controller = this;
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
controller.changePaginationDensity = function(){
controller.activePage = 1;
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
};
controller.getProperty = function(object, propertyName) {
var parts = propertyName.split('.');
for (var i = 0 ; i < parts.length; i++){
object = object[parts[i]];
}
return object;
};
controller.setActivePage = function(newActivePage){
if(newActivePage !== controller.activePage &&
newActivePage >= 1 && newActivePage <= controller.numberOfPages){
controller.activePage = newActivePage;
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
};
initialize();
$scope.$watch(function () {
return controller.numberOfItems;
}, function () {
controller.numberOfPages =
computeNumberOfPages(controller.numberOfItems, controller.density);
});
function computeNumberOfPages(numberOfItems, density){
var ceilPage = Math.ceil(numberOfItems / density);
return ceilPage !== 0 ? ceilPage : 1;
}
function initialize(){
if(controller.options.pagination){
console.log('paginate');
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
}]
)
.directive('featuredTable', [function() {
return {
'restrict': 'E',
'scope': {
'metadataArray': '=',
'itemsArray': '=',
'options': '=',
'numberOfItems': '=',
'changePaginationCallback': '&'
},
'controller': 'FeaturedTableCtrl',
'bindToController': true,
'controllerAs': 'featuredTable',
'templateUrl': 'directives/featuredTable/featuredTable.tpl.html'
};
}]);
You can see at the beginning of the controller that I'm initializing its properties with the attributes passed by the directive or providing default values:
controller.activePage = 1;
controller.changePaginationCallback =
controller.changePaginationCallback || function(){};
controller.density = 10;
controller.itemsArray = controller.itemsArray || [];
controller.metadataArray = controller.metadataArray || [];
controller.numberOfItems = controller.numberOfItems || 0;
controller.numberOfPages = 1;
controller.options = controller.options || {
'pagination': false
};
At the end I'm executing the initialize(); function that will execute the callback according to the options:
function initialize(){
if(controller.options.pagination){
controller.changePaginationCallback({
'page': controller.activePage,
'perPage': controller.density
});
}
}
I'm now trying to unit test this controller (with karma and jasmine) and I need to "simulate" the parameters passed by the directive, I tried the following:
'use strict';
describe('Controller: featured table', function () {
beforeEach(module('directives.featuredTable'));
var scope;
var featuredTable;
var createCtrlFn;
beforeEach(inject(function ($controller, $rootScope) {
scope = $rootScope.$new();
createCtrlFn = function(){
featuredTable = $controller('FeaturedTableCtrl', {
'$scope': scope
});
scope.$digest();
};
}));
it('should initialize controller', function () {
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': false
});
});
it('should initialize controller with pagination', function () {
scope.changePaginationCallback = function(){};
spyOn(scope, 'changePaginationCallback').and.callThrough();
scope.options = {
'pagination': true
};
createCtrlFn();
expect(featuredTable.activePage).toEqual(1);
expect(featuredTable.changePaginationCallback)
.toEqual(jasmine.any(Function));
expect(featuredTable.density).toEqual(10);
expect(featuredTable.itemsArray).toEqual([]);
expect(featuredTable.metadataArray).toEqual([]);
expect(featuredTable.numberOfPages).toEqual(1);
expect(featuredTable.numberOfItems).toEqual(0);
expect(featuredTable.options).toEqual({
'pagination': true
});
expect(featuredTable.changePaginationCallback).toHaveBeenCalledWith({
'page': 1,
'perPage': 10
});
});
});
And got the following error, meaning that scope is not well initialized:
Expected Object({ pagination: false }) to equal Object({ pagination: true })
at test/spec/app/rightPanel/readView/historyTab/historyTab.controller.spec.js:56
Simulating the bindings would be non-trivial - after all, it's hard to really know what compiling and linking a directive does with the data passed to it...unless you just do it yourself!
The angular.js documentation offers a guide on how to compile and link a directive for unit testing - https://docs.angularjs.org/guide/unit-testing#testing-directives. After doing that, you'd just need to get the controller from the resulting element(see the documentation for the controller() method here - https://docs.angularjs.org/api/ng/function/angular.element) and perform your tests. ControllerAs would be irrelevant here - you would be testing the controller directly, instead of manipulating the scope.
Here's an example module:
var app = angular.module('plunker', []);
app.controller('FooCtrl', function($scope) {
var ctrl = this;
ctrl.concatFoo = function () {
return ctrl.foo + ' world'
}
})
app.directive('foo', function () {
return {
scope: {
foo: '#'
},
controller: 'FooCtrl',
controllerAs: 'blah',
bindToController: true,
}
})
And test setup:
describe('Testing a Hello World controller', function() {
ctrl = null;
//you need to indicate your module in a test
beforeEach(module('plunker'));
beforeEach(inject(function($rootScope, $compile) {
var $scope = $rootScope.$new();
var template = '<div foo="hello"></div>'
var element = $compile(template)($scope)
ctrl = element.controller('foo')
}));
it('should produce hello world', function() {
expect(ctrl.concatFoo()).toEqual('hello world')
});
});
(Live demo: http://plnkr.co/edit/xoGv9q2vkmilHKAKCwFJ?p=preview)
I'm having problems creating a variable and using it in a MEAN package. I'm basing it off of the "articles" package that comes as an example. Everything I see is the same in the client-side controller, but I'm not sure why I'm catching the error when I try to start my app (with grunt) on the "books" package but not the "articles" package.
I have not implemented all the controllers that articles has yet, that may be an issue?
When I start the app with grunt, I get this error on : 'book' is defined but never used MEAN stack controller
I believe the error is in the controller, but if you need to see other files please let me know.
books.js
//client-side controller
'use strict';
angular.module('mean.books').controller('BooksController', ['$scope', 'Global', 'Books',
function($scope, Global, Books) {
$scope.global = Global;
$scope.package = {
name: 'books'
};
$scope.hasAuthorization = function(book) {
if (!book || !book.user) return false;
return $scope.global.isAdmin || book.user._id === $scope.global.user._id;
};
$scope.create = function(isValid) {
if (isValid) {
var book = new Books({
title: this.title,
author: this.author,
description: this.description,
seller: this.seller
});
/* Not sure if we need this location thing
book.$save(function(response) {
$location.path('books/' + response._id);
});
*/
this.title = '';
this.content = '';
this.description = '';
this.seller = ''; // or this.user implement
} else {
$scope.submitted = true;
}
};
}
]);
articles.js //this is the example that I'm basing it from
'use strict';
angular.module('mean.articles').controller('ArticlesController', ['$scope', '$stateParams', '$location', 'Global', 'Articles',
function($scope, $stateParams, $location, Global, Articles) {
$scope.global = Global;
$scope.hasAuthorization = function(article) {
if (!article || !article.user) return false;
return $scope.global.isAdmin || article.user._id === $scope.global.user._id;
};
$scope.create = function(isValid) {
if (isValid) {
var article = new Articles({
title: this.title,
content: this.content
});
article.$save(function(response) {
$location.path('articles/' + response._id);
});
this.title = '';
this.content = '';
} else {
$scope.submitted = true;
}
};
$scope.remove = function(article) {
if (article) {
article.$remove(function(response) {
for (var i in $scope.articles) {
if ($scope.articles[i] === article) {
$scope.articles.splice(i, 1);
}
}
$location.path('articles');
});
} else {
$scope.article.$remove(function(response) {
$location.path('articles');
});
}
};
$scope.update = function(isValid) {
if (isValid) {
var article = $scope.article;
if (!article.updated) {
article.updated = [];
}
article.updated.push(new Date().getTime());
article.$update(function() {
$location.path('articles/' + article._id);
});
} else {
$scope.submitted = true;
}
};
$scope.find = function() {
Articles.query(function(articles) {
$scope.articles = articles;
});
};
$scope.findOne = function() {
Articles.get({
articleId: $stateParams.articleId
}, function(article) {
$scope.article = article;
});
};
}
]);
In $scope.create function you defined book
var book = new Books({
and never use it. That's reason you get warning. If you want to skip jshint warnings in development use grunt -f or allow unused variables in your grunt configuration (or .jshintrc if you use it)
Using this angular modal service:
app.service('modalService', ['$modal',
function ($modal) {
var modalDefaults = {
backdrop: true,
keyboard: true,
modalFade: true,
templateUrl: '/templates/modal.html'
};
var modalOptions = {
closeButtonText: 'Close',
actionButtonText: 'OK',
headerText: 'Proceed?',
bodyText: 'Perform this action?'
};
this.showModal = function (customModalDefaults, customModalOptions) {
if (!customModalDefaults) customModalDefaults = {};
customModalDefaults.backdrop = 'static';
return this.show(customModalDefaults, customModalOptions);
};
this.show = function (customModalDefaults, customModalOptions) {
//Create temp objects to work with since we're in a singleton service
var tempModalDefaults = {};
var tempModalOptions = {};
//Map angular-ui modal custom defaults to modal defaults defined in service
angular.extend(tempModalDefaults, modalDefaults, customModalDefaults);
//Map modal.html $scope custom properties to defaults defined in service
angular.extend(tempModalOptions, modalOptions, customModalOptions);
if (!tempModalDefaults.controller) {
tempModalDefaults.controller = function ($scope, $modalInstance) {
$scope.modalOptions = tempModalOptions;
$scope.modalOptions.ok = function (result) {
$modalInstance.close(result);
};
$scope.modalOptions.close = function () {
$modalInstance.dismiss('cancel');
};
}
}
return $modal.open(tempModalDefaults).result;
};
}]);
I'm having trouble understanding how to pass values from the modal (which has an input) to the controller.
This is my modal:
<input type="text" class="form-control" id="{{modalOptions.inputName}}" name="{{modalOptions.inputName}}" data-ng-model="modalOptions.inputVal" data-ng-if="modalOptions.inputName" />
<button type="button" class="btn"
data-ng-click="modalOptions.close()">{{modalOptions.closeButtonText}}</button>
<button class="btn btn-primary"
data-ng-click="modalOptions.ok();">{{modalOptions.actionButtonText}}</button>
Controller:
$scope.addTopic = function () {
var modalOptions = {
closeButtonText: 'Cancel',
actionButtonText: 'Create Topic',
inputName: 'topicName'
};
modalService.showModal({}, modalOptions).then(function (result) {
// I tried...
var input = $scope.inputName; // and...
input = result;
$log.log("Adding topic '" + input + "' to publication no " + $scope.publication.id);
});
}
So the input is an option in modalOptions but when the user enters a value and clicks ok, nothing is sent to the controller. $scope.inputName returns undefined and so does result.
Ideally, I want to end up with an object like so { inputs : {name: 'inputName' , value: 'abcde'} }.
Try the resolve method in Angular UI Bootstrap
var modalOptions = {
resolve: {
myvar: function () {
return $scope.myvar;
}
}
};
modalService.showModal(modalOptions);