Angularjs Dynamic Controller with Dynamic Parameter - javascript

I have use requirejs, angularamd and ui.bootstrap in my project. In case of popup form I have $uibModal from ui.bootstrap. But I cannot pass a parameter "items" from resolve. How can I inject parameters for controller which have resolved dynamically?
function open(size, parentSelector) {
var parentElem = parentSelector ?
angular.element($document[0].querySelector('.grid ' + parentSelector)) : undefined;
var modalInstance = $uibModal.open({
animation: vm.animationsEnabled,
ariaLabelledBy: 'modal-title',
ariaDescribedBy: 'modal-body',
size: size,
appendTo: parentElem,
templateUrl: 'Views/Shared/ColSetting.html',
resolve: {
load: ['$q','$rootScope',function ($q, $rootScope) {
var loadController = "Views/Shared/ColSettingController";
var deferred = $q.defer();
require([loadController], function () {
deferred.resolve(items);
$rootScope.$apply();
});
return deferred.promise;
}]
}
});
This is controller I want to call.
'use strict';
define(['application-configuration', 'ajaxService'], function (app) {
function ColSettingController(items) {
var vm = this;
//vm.content = $rootScope.content;
vm.ok = function () {
//$uibModalInstance.close(vm.selected.item);
};
vm.cancel = function () {
//$uibModalInstance.dismiss('cancel');
};
}
app.register.controller("ColSettingController", ColSettingController);
});

According to ui.bootstrap, resolve property is a map object. The map object contains key/value pairs of:
key – {string}: a name of a dependency to be injected into the controller.
factory - {string|function}:
If string, then it is an alias for a service.
Otherwise if function, then it is injected and the return value is treated as the dependency. If the result is a promise, it is resolved before the controller is instantiated and its value is injected into the controller.
In your case, you are using load, but your controller expect items to be injected, I assume it fails saying it can't find items, right? It is because what you are injecting is load.
You need to change the name of your property in the resolve, load for items.
resolve: {
//change 'load' for 'items' here
items: [....rest of your code goes here....]
Also, it is recommended to use the property $inject before declaring controllers/components and others, something like this:
function ColSettingController(items) {
//some code here
}
ColSettingController.$inject = ['items'];
app.register.controller("ColSettingController", ColSettingController);
Hope it helps

Related

AngularJS accessing parent controller properties from modal controller - resolve vs $scope

Learning AngularJS is a Work In Progress for me so I just want to understand why/when we should use one over another in particular case below. Is it just matter of taste or more than that? See examples below.
In both cases, when user clicks OK button, create() function of parent controller is called from child controller.
RESOLVE STYLE
CreateController
...
var vm = this;
vm.create = create;
function create() {
console.log('Created!');
}
vm.openCreateModal = function() {
vm.modalInstance = $uibModal.open({
...
resolve: {
create: function() {
return create;
},
// Others if any
}
});
}
...
CreateModalController
...
vm.ok = function() {
create();
$uibModalInstance.close('ok');
};
...
SCOPE STYLE
CreateController
...
var vm = this;
vm.create = create;
function create() {
console.log('Created!');
}
vm.openCreateModal = function() {
vm.modalInstance = $uibModal.open({
...
scope: $scope,
resolve: {
}
});
}
...
CreateModalController
...
vm.ok = function() {
$scope.$parent.vm.create();
$uibModalInstance.close('ok');
};
...
Update
The actual reason why I ask this question is, accessining/injecting parent/root/container like objects of one service/controller in/to another controller/service is considered as a "bad practise" in some languages/frameworks I use.
The idea of resolve is that it will run that first before initializing the rest of your code. Generally you would use resolve in your routing like so:
$routeProvider
.when('/', {
templateUrl: "views/view.html",
caseInsensitiveMatch: true,
resolve: {
load: function() {
localStorage['Location'] = "/View";
}
}
})
In the above example resolve will fire the load function before my controller is ever initialized. On the other hand scope is used to bind directly to something in a controller or directive. You should use scope when triggering functions and binding to values between controllers and directives.
To add to this based on the comments below, if the resolve fails it will reject the modal and the window will not open.

pass parameter to angular component after ajax request

I am dealing with angular 1 component, I made a datatable component which accepts a dataset as a parameter.
here is how I am using datatable component.
index.html
...
<datatable dataset="ViewModel.dataset"></datatable>
...
index.controller.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.controller('PagesIndexController', PagesIndexController);
function PagesIndexController() {
var self = this;
self.dataset = {};
Restangular.one('someurl').get().then(function( pages ) {
self.dataset = pages;
});
}
})();
datatable.component.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.component('datatable', {
bindings: {
dataset: '<'
},
templateUrl: '/frontend/templates/dashboard/components/data-table.html',
controller: 'DataTableController'
});
})();
datatable.controller.js
(function() {
'use strict';
angular
.module('DashboardApplication')
.controller('DataTableController', DataTableController);
function DataTableController() {
var self = this;
console.log(self.dataset); // which is undefined!
}
})();
The problem is I'm getting undefined for dataset in datatable.controller.js. Is there any solution for this?!
Use the $onChanges life-cycle hook to see the value when it becomes defined:
angular
.module('DashboardApplication')
.controller('DataTableController', DataTableController);
function DataTableController() {
var self = this;
//console.log(self.dataset); // which is undefined!
this.$onChanges = function(changesObj) {
if (changesObj.dataset) {
console.log(changesObj.dataset.currentValue);
};
});
}
For more information, see AngularJS Developer Guide -- Components.
I think you are missing a
controllerAs: 'vm'
line from your component which will bind the model to "this" in the your controller instead of $scope (also means you can reach your viewmodel as "vm" inside your view, like:
ng-if="vm.dataset"
But I think it will still be undefined in that exact moment, you have several options here:
you can pass the promise to the component and write a then on it
you can place an ng-if="dataset && dataset.length" where you call the component in your outer html. This way the logic inside your component will only trigger when there is actually data in the property.
<datatable ng-if="ViewModel.dataset" dataset="ViewModel.dataset"></datatable>
you can also write something like this in your component:
$scope.$watch('self.dataset', function () {
if (self.dataset) {
alert ('hi');
}
});

Unit testing a directive - Unable to force a fake data

I'm trying to understand how to unit test my directive in my situation below.
Basically I'm trying to unit test a directive which has a controller. On the loading of this directive the controller makes a http request by a service which brings some data to the controller again then provides this data to the directive view.
On the scenario below in my understanding I should do:
A $httpBackend to avoid an exception when the http request is done;
Populate the fake data to be able to unit test the directive with diff behaviors
Compile the directive
What I've been trying so far, as you can see, is override the Service with the fake data. What I could not make work so far.
Some doubts come up now.
As you can see in my Controller. I'm providing the whole Service to the view:
$scope.ItemsDataservice = ItemsDataservice;
What makes me believe that my approach to override the Service should work.
My question:
On scenario below I understand that I could override the Service to manipulate the data or even override the controller to manipulate the data by scope.
What's the right thing to do here?
Am I understand wrong?
Am I mixing the unit tests?
In my current unit test code, when I'm applying the fake data(or not), is not make any difference:
ItemsDataservice.items = DATARESULT;
ItemsDataservice.items = null;
Controller:
angular.module('app')
.controller('ItemsCtrl', function ($scope, $log, ItemsDataservice) {
$scope.ItemsDataservice = ItemsDataservice;
$scope.ItemsDataservice.items = null;
$scope.loadItems = function() {
var items = [];
ItemsDataservice.getItems().then(function(resp) {
if (resp.success != 'false') {
for (resp.something ... ) {
items.push({ ... });
};
ItemsDataservice.items = items;
};
}, function(e) {
$log.error('Error', e);
});
};
$scope.loadItems();
});
Service:
angular.module('app')
.service('ItemsDataservice', function ItemsDataservice($q, $http) {
ItemsDataservice.getItems = function() {
var d = $q.defer();
var deffered = $q.defer();
var url = 'http://some-url?someparameters=xxx'
$http.get(url)
.success(function (d) {
deffered.resolve(d);
});
return deffered.promise;
};
return ItemsDataservice;
});
Directive:
angular.module('app')
.directive('items', function () {
return {
templateUrl: '/items.html',
restrict: 'A',
replace: true,
controller: 'ItemsCtrl'
};
});
Unit testing directive:
ddescribe('Directive: Items', function () {
var element, scope, _ItemsDataservice_, requestHandler, httpBackend;
var URL = 'http://some-url?someparameters=xxx';
var DATARESULT = [{ ... }];
// load the directive's module
beforeEach(module('app'));
beforeEach(module('Templates')); // setup in karma to get template from .html
beforeEach(inject(function ($rootScope, ItemsDataservice) {
httpBackend = $httpBackend;
scope = $rootScope.$new();
_ItemsDataservice_ = ItemsDataservice;
requestHandler = httpBackend.when('GET', URL).respond(200, 'ok');
}));
afterEach(function() {
//httpBackend.verifyNoOutstandingExpectation();
//httpBackend.verifyNoOutstandingRequest();
});
it('Show "No Items available" when empty result', inject(function ($compile) {
_ItemsDataservice_.items = null;
element = angular.element('<div data-items></div>');
element = $compile(element)(scope);
scope.$digest();
element = $(element);
expect(element.find('.msg_noresult').length).toBe(1);
}));
it('Should not show "No Items available" when data available ', inject(function ($compile) {
_ItemsDataservice_.items = DATARESULT;
element = angular.element('<div data-items></div>');
element = $compile(element)(scope);
scope.$digest();
element = $(element);
expect(element.find('.msg_noresult').length).toBe(0);
}));
});
I sorted out the problem.
Changed this line:
element = $compile(element)(scope);
To this line:
element = $compile(element.contents())(scope);
The only diff is the jquery method .contents()
I did not get yet why. But it solved.
Update:
Another thing I've just discovered and that was really useful for me.
You can use regular expression on you httpBackend:
httpBackend.whenGET(/.*NameOfThePageXXX\.aspx.*/).respond(200, 'ok');
So, you don't need to worry to use exactly the same parameters etc if you just want to avoid an exception.

How do you store a JSON object as a function parameter in Angular

I am using a directive to populate a chart using the AmCharts API. The function that is currently being used lists out the data that is populating the form in JSON format as a parameter in the function. I want to be able to store this as a variable so I can have a separate file for the JSON. I know you probably have to use $http to get the json, but I am unsure how you would connect this to the directive.
var myapp = angular.module('tmn_portfolio', []);
myapp.directive('loadPortfolio',
function () {
return {
restrict: 'E',
replace:true,
template: '<div id="chartdiv" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
chart = AmCharts.makeChart("chartdiv", I WANT THIS TO BE A VARIABLE TO JSON FILE);
};
initChart();
}//end watch
}
}) ;
A better solution than scope: true would be to pass the data to the directive in an attribute. The markup will look like this:
<load-portfolio my-data="jsonData" ></load-portfolio>
That element will be replaced by your template. In the directive:
myapp.directive('loadPortfolio',
function () {
return {
restrict: 'E',
replace:true,
scope: {
myData: '='
},
template: '<div id="chartdiv" style="min-width: 310px; height: 400px; margin: 0 auto"></div>',
link: function (scope, element, attrs) {
var chart = false;
var initChart = function() {
if (chart) chart.destroy();
var config = scope.config || {};
chart = AmCharts.makeChart("chartdiv", scope.myData);
};
initChart();
}//end watch
}
});
The equals sign is a two way binding between the property myData on the directive scope, and the property jsonData on the scope of your controller (the "model" in angular terms). Any time the data changes in the controller, it will also change in the directive, and your UI will update to reflect that.
Now you just have to get the json data, right? You are right, you should probably use $http to do this, and what that will look like will depend on your specific implementation, but the important part is that once you populate jsonData, your directive will be updated to reflect that. I typically initialize models that will be populated asynchronously in my controller. A very simple version might look something like this:
myapp.controller('myController', ['$scope', '$http', function($http, $scope) {
$scope.jsonData = {}; // or '' - depending on whether you actually want the JSON string here.
$http({
url: '/data.json',
method: 'GET'
}).
then(function(r) {
$scope.jsonData = r.data;
});
}]);
I think that to do it properly you should look into routing and the resolve property of routes which allows you to retrieve the data before your controller loads, and pass it as an argument to the controller, but that is really up to you.
Update: Like the other answer, I do recommend using a factory/service for calls to your server or API, instead of using $http directly in the controller like in my example. Just trying to keep it simple here. Angular code is more fun when well-organized.
You need a factory or service to first request your JSON file and store it as a variable:
var app = angular.module('app', []);
app.config(['$httpProvider', function($httpProvider) {
$httpProvider.defaults.useXDomain = true;
delete $httpProvider.defaults.headers.common['X-Requested-With'];
}
]);
app.factory('data', function($http) {
var data = {
async: function() {
// $http returns a promise, which has a then function, which also returns a promise
var promise = $http.get('file.json').then(function (response) {
// The then function here is an opportunity to modify the response
console.log(response);
// The return value gets picked up by the then in the controller.
return response.data;
});
// Return the promise to the controller
return promise;
}
};
return data;
});
app.controller('MainCtrl', function( data,$scope) {
// Call the async method and then do stuff with what is returned inside our own then function
data.async().then(function(d) {
$scope.jsonData = d; ;
});
});

How to include/inject functions which use $scope into a controller in angularjs?

I am trying to include a library of functions, held in a factory, into a controller.
Similar to questions like this:
Creating common controller functions
My main controller looks like this:
recipeApp.controller('recipeController', function ($scope, groceryInterface, ...){
$scope.groceryList = [];
// ...etc...
/* trying to retrieve the functions here */
$scope.groceryFunc = groceryInterface; // would call ng-click="groceryFunc.addToList()" in main view
/* Also tried this:
$scope.addToList = groceryInterface.addToList();
$scope.clearList = groceryInterface.clearList();
$scope.add = groceryInterface.add();
$scope.addUp = groceryInterface.addUp(); */
}
Then, in another .js file, I have created the factory groceryInterface. I've injected this factory into the controller above.
Factory
recipeApp.factory('groceryInterface', function(){
var factory = {};
factory.addToList = function(recipe){
$scope.groceryList.push(recipe);
... etc....
}
factory.clearList = function() {
var last = $scope.prevIngredients.pop();
.... etc...
}
factory.add = function() {
$scope.ingredientsList[0].amount = $scope.ingredientsList[0].amount + 5;
}
factory.addUp = function(){
etc...
}
return factory;
});
But in my console I keep getting ReferenceError: $scope is not defined
at Object.factory.addToList, etc. Obviously I'm guessing this has to do with the fact that I'm using $scope in my functions within the factory. How do I resolve this? I notice that in many other examples I've looked at, nobody ever uses $scope within their external factory functions. I've tried injecting $scope as a parameter in my factory, but that plain out did not work. (e.g. recipeApp.factory('groceryInterface', function(){ )
Any help is truly appreciated!
Your factory can't access your $scope, since it's not in the same scope.
Try this instead:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.addToList = groceryInterface.addToList;
$scope.clearList = groceryInterface.clearList;
$scope.add = groceryInterface.add;
$scope.addUp = groceryInterface.addUp;
}
recipeApp.factory('groceryInterface', function () {
var factory = {};
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
factory.clearList = function() {
var last = this.prevIngredients.pop();
}
});
Alternatively, you can try using a more object oriented approach:
recipeApp.controller('recipeController', function ($scope, groceryInterface) {
$scope.groceryFunc = new groceryInterface($scope);
}
recipeApp.factory('groceryInterface', function () {
function Factory ($scope) {
this.$scope = $scope;
}
Factory.prototype.addToList = function (recipe) {
this.$scope.groceryList.push(recipe);
}
Factory.prototype.clearList = function() {
var last = this.$scope.prevIngredients.pop();
}
return Factory;
});
You cannot use $scope in a factory as it is not defined. Instead, in your factory functions change the properties of the object the factory is returning, e.g.
factory.addToList = function (recipe) {
this.groceryList.push(recipe);
}
these will then get passed on to your $scope variable
$scope.addToList = groceryInterface.addToList;
// ... = groceryInterface.addToList(); would assign to `$scope.addToList` what is returned, instead of the function itself.
This isn't the exact answer for this question, but I had a similar issues that I solved by simply passing $scope as an argument to a function in my factory. So it won't be the normal $scope, but $scope at the time the function in the factory is called.
app.controller('AppController', function($scope, AppService) {
$scope.getList = function(){
$scope.url = '/someurl'
// call to service to make rest api call to get data
AppService.getList($scope).then(function(res) {
// do some stuff
});
}
});
app.factory('AppService', function($http, $q){
var AppService = {
getList: function($scope){
return $http.get($scope.url).then(function(res){
return res;
});
},
}
return AppService;
});

Categories