Parametrise controller to work in different directives - angularjs

I need to work with several different directives in angularJS. The directives have different templates and the controllers should work in very similar way, with very small changes each-other.
I was thinking to use just one shared controller that adapt its behaviour to the directive it's included like the idea described in code:
var module = angular.module('app', []);
module.directive('myFirstDirective', function () {
return {
scope: {},
controller: 'MyController',
templateUrl: './directives/first.html'
};
});
module.directive('mySecondDirective', function () {
return {
scope: {},
controller: 'MyController',
templateUrl: './directives/second.html'
};
});
module.controller('MyController', function ($scope) {
$scope.myEvent = function () {
//if it's first directive do this
//if it's second directive do that
};
});
Is there any way in angular to do this?

You can use ng-init="callback1()" and ng-init="callback2()" in your directives. And describe both callbacks in your controller.

Related

angularJS controller property injection

Is is possible to use property injection in angularJS?
Scenario
I know this will work
app
.module('myapp')
.config(function($stateProvider) {
$stateProvider.state('the-state', {
url: '/the/url',
templateUrl: 'view.html',
controller: 'ctrl',
controllerAs: 'vm',
resolve: {
'boolFlag': function(service){return service.getBoolean();}
}
});
})
.service('service', function(){
this.getBoolean = function() {return ...};
})
.controller('ctrl', function(boolFlag) {
this.boolFlag = boolFlag;
this.execute = function(){...};
});
<div ng-show="vm.boolFalg">
simple dynamic content
</div>
<button type="button" ng-click="vm.execute()">Click Me</button>
But it feels leaky. boolFlag` is only used in the view to show/hide content. Is there I way I can resolve the controller and set the boolFlag property on the controller instance? I assume providers or factories would be the way to go, but I'm going around in circles trying to make sense of it.
I envision it would look something like
app
.module('myapp')
.config(function($stateProvider) {
$stateProvider.state('the-state', {
url: '/the/url',
templateUrl: 'view.html',
controller: 'ctrl',
controllerAs: 'vm',
});
})
.provider('ctrlProvider', function(ctrlProvider, service) {
var ctrl = ctrlProvider.$get/invoke/build/create();
ctrl.boolFlag = service.getBoolean();
return ctrl;
})
.service('service', function(){
this.getBoolean = function() {return ...};
})
.controller('ctrl', function() {
this.execute = function(){...};
});
I could also be going about this the wrong way. Many controllers will need the boolFlag property. Maybe it should be part of a $parentScope? But I don't know how to code that.
Update
I was thinking about this more last night. The boolFlag doesn't need to be associated to the controller at all. It only needs to be part of the scope. $scope.boolFlag = service.getBoolean();
The question then becomes, how can I populate $scope without the controller?
you could use factory to maintain the value of boolFlag so that it is shared between the controllers. It is better to avoid using $parentScope. This explains how to proceed with that. If you need to set the value of factory initially you could also use it in app.config of your main module to set its value.

Calling a controller inside a factory method

Currently I have a factory method like this:
open_model: function(som_id){
var modelSettings = $aside({
controller: // a very big controller code
size: 'sm',
placement: 'right',
templateUrl: 'views/id/modal.html',
resolve: {
modal_data: function(){
return area_id;
}
}
});
},
I want to separate out the controller from this method and call it by name. Currently this is >100 lines of code in controller section and hence it makes factory dirty. So is there a way to define the controller somewhere else, and call the controller by name instead of writing the code.
In addition to #nikjohn's answer, you can also create a separate angular controller and refer it within you modelSettings. Since you already said your controller code for the modal exceeds 100 lines, this even might be a better solution.
angular.module('myApp').controller('ModalInstanceCtrl', ['$scope', 'modal_data', function($scope, modal_data) {
// modal controller code goes here
}]);
// and refer it in your factory method
open_model: function(som_id){
var modelSettings = $aside({
controller: 'ModalInstanceCtrl'
size: 'sm',
placement: 'right',
templateUrl: 'views/id/modal.html',
resolve: {
modal_data: function(){
return area_id;
}
}
});
}
You can define your controller wherever you want, and inject it as a dependency into your factory (although I don't know what a controller would be doing within a factory).
controllerFn: function(a, b) {
// ......
}
open_model: function(som_id, controllerFn){
var modelSettings = $aside({
controller: controllerFn,
size: 'sm',
placement: 'right',
templateUrl: 'views/id/modal.html',
resolve: {
modal_data: function(){
return area_id;
}
}
});
},
You should never access a controller within a factory. You're mixing concerns by doing that. If there's functionality you want to separate out, create another service and inject that service into your factory. Your controller should be leveraging services and factories, not the other way around.

Angular Component Bindings Undefined

I'm trying to put together my first angular component with ngRoute and so far I'm unable to get data to resolve.
config:
.when('/myfirstcomponent', {
template: '<myfirstcomponent claimKeys="$resolve.claimKeys"></myfirstcomponent>',
resolve: {
claimKeys: ['$http', function($http) {
$http.get('server/claimkeys.json').then((response) => {
var claimKeys = response.data.DATASET.TABLE;
return claimKeys;
});
}]
}
})
Component:
.component('myfirstcomponent', {
bindings: {
'claimKeys': '#'
},
templateUrl: 'components/component.html',
controller: [function() {
this.$onInit = function() {
var vm = this;
console.log(vm.claimKeys);
};
}]
The html for the component simply has a p element with some random text that's all.
I can see when debugging that I am retrieving data but I cannot access it on the component controller...
EDIT: Thanks to the accepted answer below I have fixed my issue. It didn't have anything to do with an issue with asynchronous calls but with how I had defined my route and the component. See below code for fix. Thanks again.
some issues:
as you said claimKeys within directive should be claim-keys
its binding should be '<' (one way binding) or '=' (two way binding), but not '#' which just passes to directive a string found between quotes
in your directive's controller var vm = this; should be above
$onInit function and not inside it (the scopes are different)
resolve.claimkeys should return $http's promise and not just call
it
claimKeys should be received by router's controller as injection and passed to its template
controllerAs: '$resolve' should be used by router
app.component('myfirstcomponent', {
bindings: {
'claimKeys': '='
},
template: 'components/component.html',
controller: function() {
var vm = this;
this.$onInit = function() {
console.log(vm.claimKeys);
};
}
});
app.config(function ($stateProvider) {
$stateProvider.state('myfirstcomponent', {
url: '/myfirstcomponent',
template: '<myfirstcomponent claim-keys="$resolve.claimKeys"></myfirstcomponent>',
resolve: {
claimKeys: ['$http', function($http) {
return $http.get('claimkeys.json').then((response) => {
return response.data.DATASET.TABLE;
});
}]
},
controller: function (claimKeys) {
this.claimKeys = claimKeys;
},
controllerAs: '$resolve'
})
});
plunker: http://plnkr.co/edit/Nam4D9zGpHvdWaTCYHSL?p=preview, I used here .state and not .when for routing.

angularjs 1.5 component dependency injection

this may sound newb, but I have been following this tutorial for angularjs component.
I am new to components and how do I inject a constant Utils or authService to my component like this?
app.component('tlOverallHeader', {
bindings: {
data: '='
},
templateUrl: 'js/indexTimeline/components/tl_overallHeader/templates/tl_overallHeader.html',
controller: function() {
this.ms = 'tlOverallheader!'
}
})
thanks!
You can inject services to component controller like this:
angular.module('app.module')
.component('test', {
templateUrl: 'views/someview.html',
bindings: {
subject: '='
},
controller: ['$scope', 'AppConfig', TestController]
});
function TestController(scope, config) {
scope.something = 'abc';
}
or like this:
angular.module('app.module')
.component('test', {
templateUrl: 'views/someview.html',
bindings: {
subject: '='
},
controller: TestController
});
TestController.$inject = ['$scope', 'AppConfig']
function TestController(scope, config) {
scope.something = 'abc';
}
You should be able to inject services into your component's controller just like a standalone controller:
controller: function(Utils, authService) {
this.ms = 'tlOverallheader!'
authService.doAuthRelatedActivities().then(...);
}
The accepted answer isn't minification safe. You can use the minification-safe dependency injection notation here too:
controller: ['Utils', 'authService',
function(Utils, authService) {
this.ms = 'tlOverallheader!'
authService.doAuthRelatedActivities().then(...);
},
]
For Functional style programming which utilizes Factory style services the following syntax gets the job done:
angular.module('myApp')
.component('myComponent', {
templateUrl: 'myTemplate.html',
controller: ['myFactory', function(myFactory){
var thisComponent = this;
thisComponent.myTemplatemsg = myFactory.myFunc();
}]
})
.factory('myFactory', [ function(){
return {
myFunc: function(){
return "This message is from the factory";
}
};
}]);
A word of caution: The same component service/factory you setup for your component is also injectable (and thus accessible) anywhere else in your app including the parent scope and other component scopes. This is powerful but can be easily abused. Hence, it is recommended components only modify data within their own scope so there's no confusion on who is modifying what. For more on this see https://docs.angularjs.org/guide/component#component-based-application-architecture .
However, even the discussion in the aforementioned link only addresses the
cautionary use of the two-way-binding property value of '='when using the bindings object. Therefore the same reasoning should apply for component services and factories. If you plan on using the same service or factory in other component scopes and/or the parent scope I recommend setting up your service/factory where your parent scope resides or where your intended congregation of services/factories reside. Especially if you have numerous components using the same service/factory. You don't want it located in some arbitrary component module of which would be hard to find.

Flotcharts not rendering with angularjs

I am having issues rendering Flotcharts with data populated from an AngularJS (v1.2.5) service call. When the data is hard-coded, the charts render as expected but not when assigned the value from a $resource call (console logging shows "undefined" as the value).
The odd thing is that the same variable I use to populate the chart data is displayed in the view and works fine which makes me think this could be some sort of race condition or scoping issue.
I have tried assigning the the $scope variables as a result of resolved promises without success to remedy race conditions as well as tried things such as $scope.$apply() from the chart directive.
Below are code excerpts from my application. Any help is appreciated. Thank you.
HTML wrapper:
<!DOCTYPE html>
<html data-ng-app="app" data-ng-controller="app">
<head>
<title>FlotChart Example</title>
</head>
<body>
<main data-ng-view></main>
</body>
</html>
Template from Overview route:
<div class="chart-title">{{getCount.count}} Total Transactions</div>
<div id="deposits" data-chart="pie" data-flot-chart data-ng-model="model" data-ng-controller="OverviewCtrl"></div>
Main Module ("app"):
;(function (angular) {
'use strict';
angular.module('app', ['ngRoute', 'ngResource', 'ngTouch', 'services.Localization', 'Login', 'Overview'])
.config(['$routeProvider', function ($routeProvider) {
$routeProvider
.when('/', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl'
})
.when('/overview', {
templateUrl: 'views/overview.html',
controller: 'OverviewCtrl'
})
.otherwise({
redirectTo: '/'
});
}])
.controller('App', ['$rootScope', 'Localize', function ($rootScope, Localize) {
// initialize localization
$rootScope.labels = Localize.get();
}]);
}(angular));
Overview Controller (used in charts and view):
;(function (angular) {
'use strict';
angular.module('Overview', ['services.DepositsSvc'])
.controller('OverviewCtrl', ['$rootScope', '$scope', 'DepositCount', 'DepositCountGET', function ($rootScope, $scope, DepositCount, DepositCountGET) {
$scope.getCount = DepositCount.get();
$scope.getGETCount = DepositCountGET.get();
$scope.model = {};
$scope.model.data = [
{
label: $rootScope.labels.gets,
data: $scope.getGETCount.count,
color: "#e4d672"
},
{
label: $rootScope.labels.puts,
data: $scope.getCount.count,
color: "#c2cfeb"
}
];
$scope.model.options = {
series: {
pie: {
show: true,
radius: 1,
label: {
radius: 2/3,
formatter: function (label, series) {
return '<div class="pie">' + label + ': ' +
series.data[0][1] + '<br>(' + Math.round(series.percent) + '%)</div>';
}
}
}
},
legend: {
show: false
}
};
}]);
}(angular));
Services (output from both calls is { count: number }):
;(function (angular) {
'use strict';
angular.module('services.DepositsSvc', ['ngResource', 'ngRoute'])
.factory('DepositCount', ['$resource', function ($resource) {
return $resource('/rest/deposits/count', {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}])
.factory('DepositCountGET', ['$resource', function ($resource) {
return $resource('/rest/deposits/countgetdeposits', {}, {
query: { method: 'GET', params: {}, isArray: true }
});
}]);
}(angular));
Charts directive:
;(function (angular, $) {
'use strict';
angular.module('directives.FlotCharts', [])
.directive('flotChart', function () {
return {
restrict: 'EA',
controller: ['$scope', '$attrs', function ($scope, $attrs) {
var plotid = '#' + $attrs.id,
model = $scope[$attrs.ngModel];
$scope.$watch('model', function (x) {
$.plot(plotid, x.data, x.options);
});
}]
};
});
}(angular, jQuery));
You should try to distill this down to a smaller example that fits in a jsFiddle, so we can try it out.
One thing that I notice, though, is that you're doing a shallow watch, then setting model.data and model.options. So unless I'm missing something, the watch will not fire when those change; only when model itself changes. Try passing true as the third argument to watch.
I'm not sure that would solve your problem (because this maybe the result of numerous problem) but I think you got the directive scope and model attachment wrong:
*. I'm not sure why you encapsulate the directive as a self invoking function (never saw this style before, and Sure it isn't needed.
*. you are creating a new module here with the ('directives.flotCharts',[]) syntax, without the [] you could attach the directive to any existing module. the important part is that this module isn't injected into app! you should include it in the app module dependency array. without it, angular doesn't know about this directive (and also include the js file in index.html... I sometimes forget and wonder why is this not working)
*. I would suggest rewriting the directive as (and notice the comments also):
something like this:
angular.module('directives.FlotCharts', []) // creaating a new module here
.directive('flotChart', function () {
return {o
restrict: 'EA',
scope:{model:"=ngModel"},//creating a two ways binding to ngModel
controller: ['$scope', '$attrs','$element', function ($scope, $attrs,$element) {
var plotid = '#' + $attrs.id, // you are doing an ugly hack instead of using $element, which comes with the directive, $element is a jquery (or jquery lite) object (if you included jQuery **before** angular.js in index.html it is a jQuery object)
$scope.$watch(function(){return $scope.model}, function (x) {
if (!x){
return;
}
$.plot(plotid, x.data, x.options);//may need to wrap this in an $apply, depends.. //surly this should be called on the $element object - something like: $element.plot(x.data, x.options) although I don't now the specifics of this lib.
}, true); //the true is needed for a deep equality and not just shallow check, which sometimes has problems with objects.. not sure you need this here.
}]
};
});
I also suspect you are treating promises wrong. but not sure this is the problem here (although when everything else would work this probabely could and would be a problem. to scope this answer and question I think you should refine the promises issue to a different and specific question.
Good luck!
Solution documented at AngularJS Google Group.

Resources