I wrote a small Angular1 app which has a Database service that is using LokiJS.
I was wondering if there is a way to dynamically add properties/functions to a Service/Factory.
I'm trying to add dynamic getter for every collection that is created via this Service.
Here my example:
Database.js
angular.module('MyApp')
.factory('Database', ['$log', '$q', 'Loki',
function Database($log, $q, Loki)
{
var _db,
dbInitialized = false;
function init(config)
{
// some logic here
}
function addCollection(name, cfg) {
// some logic here
_db.addCollection(name, cfg);
// this doesnt work, but is desired ->
/*this['get'+name] = this.getCollection.bind(this, name);*/
}
function getCollection(collectionName) {
// some logic here
return something;
}
return {
init: init,
addCollection: addCollection,
getCollection: getCollection
};
}
]
);
app.js
angular
.module('MyApp', ['lokijs'])
.run(['Database',
function (Database) {
Database.init();
Database.addCollection("MyCollection", {});
// then fill collection, afterwards ->
var collection = Database.getCollection("MyCollection");
// I would like to use Database.getMyCollection()
}]);;
Is there a way to modify a initialized Service/Factory?
The most appropriate place for that is decorator
app.decorator('Database', ['$delegate', function ($delegate) {
var Database = $delegate;
Database.init();
Database.addCollection("MyCollection", {});
...
return Database;
}]);
The recipe doesn't really differs from run block, but it guarantees that service will be initialized on injection, while run blocks depend on their order.
I have this snippet:
myApp.factory('productsStore', function ($http, $q, Product) {
var products = "";
products = productsStore.get();
return {
get: function () {
return Product.query({});
}
};
});
How can I call the get() method, from within the same 'factory'? products = productsStore.get() does not work obviously.
You can use the Revealing Module Pattern:
myApp.factory('productsStore', function ($http, $q, Product) {
var products = "";
var get = function () {
return Product.query({});
};
products = get();
return {
get: get
};
});
Reasons I enjoy this pattern:
No cluttering with this., MyObject., etc. prefixing.
You see clearly in the bottom return what is publicly exposed.
A great article on the subject: Mastering the Module Pattern
You could assign the object you return to a variable, then call the get function defined in your variable, then return the variable.
Either that, or just duplicate your query code.
I need a Factory object that can be applied with a $scope.property to get a result. I also need to know if either the modifier in the factory instance, or the $scope.property changes to update the result. How I am seeing this pattern might be wrong of course.
app.factory('MyFactory',
function () {
var MyFactory = function (name, modifier) {
this.name = name;
this.result = 0;
this.modifier = modifier;
}
//I might want to call this when modifier or MyProperty changes
MyFactory.prototype.modifyingMethod = function () {
this.result = this.modifier * //externalProperty;
}
MyFactory.prototype.watcher = function () {
//no idea what I will do here or if I need this at all
// I need to recalculate the result like with $watch
this.modifyingMethod();
}
return MyFactory;
}
)
app.controller('MyCtrl'
function($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = [
new MyFactory('Option1', 1),
new MyFactory('Option2', 2),
new MyFactory('Option3', 3),
new MyFactory('Option4', 4),
new MyFactory('Option5', 5)
];
}
So I have the problem that I need to $watch MyProperty and the modifier (it can be changed bu users) so I can change the result. If the Property is a value type passing it into the Factory constructor will not work. Perhaps I could pass a function in the returns MyProperty.
Can I set up internal $watch on the factory. If I would do this outside of the factory, in the controller, I would need to do it for each instance. Should I perhaps set up some sort of register method on my factory object?
Are there any suggestions, patterns or something I might want to use?
Basically you could understand your Factory as an interface to a collection of objects (either an array or associative array respectively pure javascript object).
I find your approach with Objects very appealing and I am sure you can achieve similar things. Still I put together a fiddle, that shows how I would solve the problem:
In a MVC pattern your Factory would be the model, the controller should be as simple as possible and your HTML with directives represents the view.
You controller watches for changes from the user ($scope.MyProperty with $watch). While the model is self aware of any depending external property changes. Note that the changes of the ExternalObject service/factory will only be recognizable, if those aren't primitive values. That is why in the ExternalObject factory I return the whole object.
In a perfect world you wouldn't need to listen for changes with an interval, but will receive a javascript event. If this is possible, do it! Note that object updates out of Angular's scope will need you to do a $scope.$apply(), if you want to see the changes in the view. $intervaland $timeout both call a $scope.apply(), so using them is best practice.
Actually there still has to be a lot of cleanup to be done in this code, but it might give you a basic idea how to use an alternative structure:
var app = angular.module('yourApp', []);
window.yourGlobalObject = {};
setInterval(function () {
yourGlobalObject.externalProperty = Math.floor(Math.random() * 5000);
}, 1000);
app.factory('ExternalObject', ['$window', function ($window) {
return $window.yourGlobalObject;
}]);
app.factory('MyFactory', ['$interval', 'ExternalObject', function ($interval, ExternalObject) {
var options = [{
name: 'Option1',
modifier: 1
}, {
name: 'Option2',
modifier: 2
}, {
name: 'Option3',
modifier: 3
}],
cachedExternalProperty = 0,
cachedMyProperty = 0;
MyFactory = {
getOptions: function () {
return options;
},
addOption: function (name, modifier) {
options.push({
name: name,
modifier: modifier
});
},
setMyProperty: function (value) {
cachedMyProperty = value;
},
setResults: function (myProperty) {
angular.forEach(options, function (option, key) {
option.result = option.modifier * ExternalObject.externalProperty * myProperty;
});
console.log(options);
}
};
// let the service check for updates in the external property, if changed
$interval(function () {
if (cachedExternalProperty !== ExternalObject.externalProperty) {
cachedExternalProperty = ExternalObject.externalProperty;
MyFactory.setResults(cachedMyProperty);
}
}, 1000);
return MyFactory;
}]);
app.controller('MyCtrl', ['$scope', 'MyFactory', function ($scope, MyFactory) {
$scope.MyProperty = 42;
$scope.MyFactoryOptions = MyFactory.getOptions();
$scope.setResults = function () {
MyFactory.setResults($scope.MyProperty);
};
$scope.$watch('MyProperty', function (value) {
MyFactory.setMyProperty(value)
MyFactory.setResults(value);
});
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<body>
<section ng-app="yourApp" ng-controller="MyCtrl">
<button ng-click="setResults(MyProperty)">Update Results</button>
<div ng-repeat="factory in MyFactoryOptions">{{factory.name}} {{factory.result}}</div>
<input type="number" ng-model="MyProperty">
</section>
</body>
I'm doing a small project to play around the goody bag the ES6 is bringing, I'm trying to set register a class as an angular directive, but I'm running into this error "TypeError: Cannot call a class as a function", but from the examples I'm finding they just write the class and register it with angular as a directive. Here's my directive.
class dateBlock {
constructor () {
this.template = '/app/dateblock/dateblock.html';
this.restrict = 'AE';
this.scope = {};
}
};
export default dateBlock
and my index where I import it and then declare it.
import calendarController from './calendar/calendar.js'
import dateBlock from './dateblock/dateblock.js'
function setup($stateProvider) {
$stateProvider
.state('base', {
url: '',
controller: calendarController,
templateUrl: '/app/calendar/calendar.html'
});
};
setup.$inject = ['$stateProvider']
var app = angular.module('calApp',['ngAnimate','ui.router','hmTouchEvents', 'templates'])
.config(setup)
.controller('calendarController', calendarController)
.directive('dateBlock', dateBlock)
If I missed some crucial step I'd love to hear it. Also side question is it cleaner to import all the apps components to the index and register them all there or export the app and import and register within the components?
From my point of view, there is no need to use external libraries like register.js, because you can create directive as a ES6 class in this way:
class MessagesDirective {
constructor() {
this.restrict = 'E'
this.templateUrl = 'messages.html'
this.scope = {}
}
controller($scope, $state, MessagesService) {
$scope.state = $state;
$scope.service = MessagesService;
}
link(scope, element, attrs) {
console.log('state', scope.state)
console.log('service', scope.service)
}
}
angular.module('messages').directive('messagesWidget', () => new MessagesDirective)
Using directive controller allows you to inject dependencies, even without additional declaration (ex. MessagesDirective.$inject = ['$scope', '$state', 'MessagesService']), so you can use services in link function via scope if you need.
As mentioned in a comment, the module.directive() method expects a factory function rather than a constructor.
The most simple way would be to wrap your class in a function that returns an instance:
angular.module('app')
.directive('dateBlock', () => new DateBlock());
However, this will only work in the most limited sense - it does not allow for dependency injection and the compile and link functions of your directive (if defined) will not work as expected.
In fact, this is a problem I have looked into quite extensively and it turned out to be fairly tricky to solve (for me at least).
I wrote an extensive article covering my solution, but as far as you are concerned I can point you to the discussion of the two main issues that need to be resolved:
Dynamically converting a class definition into an angular-compatible factory function
Allowing a directive's link and compile functions to be defined as class methods
The full solution involves too much code to paste here, I think, but I have put together a working demo project which allows you to define a directive as an ES6 class like this:
class MyDirective {
/*#ngInject*/
constructor($interval) {
this.template = '<div>I\'m a directive!</div>';
this.restrict = 'E';
this.scope = {}
// etc. for the usual config options
// allows us to use the injected dependencies
// elsewhere in the directive (e.g. compile or link function)
this.$interval = $interval;
}
// optional compile function
compile(tElement) {
tElement.css('position', 'absolute');
}
// optional link function
link(scope, element) {
this.$interval(() => this.move(element), 1000);
}
move(element) {
element.css('left', (Math.random() * 500) + 'px');
element.css('top', (Math.random() * 500) + 'px');
}
}
// `register` is a helper method that hides all the complex magic that is needed to make this work.
register('app').directive('myDirective', MyDirective);
Check out the demo repo here and here is the code behind register.directive()
#Michael is right on the money:
the module.directive() method expects a factory function
However I solved it using another technique, a little cleaner I suppose, It works fine for me, it's not perfect though...
I defined a static method that returns a the factory expected by module()
class VineDirective {
constructor($q) {
this.restrict = 'AE';
this.$q = $q;
}
link(scope, element, attributes) {
console.log("directive link");
}
static directiveFactory($q){
VineDirective.instance = new VineDirective($q);
return VineDirective.instance;
}
}
VineDirective.directiveFactory.$inject = ['$q'];
export { VineDirective }
And in my app I do:
angular.module('vineyard',[]).directive('vineScroller', VineDirective.directiveFactory)
I believe there's no other way to use classes + directives that going through hacks like this at this point, just pick the easy one ;-)
A simpler, cleaner and more readable solution 🚀.
class ClipBoardText {
constructor() {
console.log('constructor');
this.restrict = 'A';
this.controller = ClipBoardTextController;
}
link(scope, element, attr, ctr) {
console.log('ctr', ctr);
console.log('ZeroClipboard in link', ctr.ZeroClipboard);
console.log('q in link', ctr.q);
}
static directiveFactory() {
return new ClipBoardText();
}
}
// do not $inject like this
// ClipBoardText.$inject = ['$q'];
class ClipBoardTextController {
constructor(q) {
this.q = q;
this.ZeroClipboard = 'zeroclipboard';
}
}
ClipBoardTextController.$inject = ['$q'];
export default ClipBoardText.directiveFactory;
You cannot get $q in link function, this in link will be undefined or null. exploring-es6-classes-in-angularjs-1-x#_section-factories
when Angular invokes the link function, it is no longer in the context of the class instance, and therefore this.$interval will be undefined
So make use of the controller function in the directive, and inject dependencies or anything that you want to access in the link function.
My solution:
class myDirective {
constructor( $timeout, $http ) {
this.restrict = 'E';
this.scope = {};
this.$timeout = $timeout;
this.$http = $http;
}
link() {
console.log('link myDirective');
}
static create() {
return new myDirective(...arguments);
}
}
myDirective.create.$inject = ['$timeout', '$http'];
export { myDirective }
and in the main app file
app.directive('myDirective', myDirective.create)
In my project I use a syntax sugar for injections. And ES6 makes it pretty simple to use injectable factories for directives avoiding too much duplicate code. This code allows injection inheritance, uses annotated injections and so on. Check this:
First step
Declare base class for all angular controllers\directives\services - InjectableClient. Its main task - set all injected params as properties for 'this'. This behavior can be overridden, see examples below.
class InjectionClient {
constructor(...injected) {
/* As we can append injections in descendants we have to process only injections passed directly to current constructor */
var injectLength = this.constructor.$inject.length;
var injectedLength = injected.length;
var startIndex = injectLength - injectedLength;
for (var i = startIndex; i < injectLength; i++) {
var injectName = this.constructor.$inject[i];
var inject = injected[i - startIndex];
this[injectName] = inject;
}
}
static inject(...injected) {
if (!this.$inject) {
this.$inject = injected;
} else {
this.$inject = injected.concat(this.$inject);
}
};
}
For example, if we call SomeClassInheritedFromInjectableClient.inject('$scope'), in directive or controller we will use it as 'this.$scope'
Second step
Declare the base class for directive with static method "factory()", which binds $injected property of directive class to factory function. And also "compile()" method, which binds the context of link function to the directive itself. Its allows to use our injected values inside the link function as this.myInjectedService.
class Directive extends InjectionClient {
compile() {
return this.link.bind(this);
}
static factory() {
var factoryFunc = (...injected) => {
return new this(...injected);
}
factoryFunc.$inject = this.$inject;
return factoryFunc;
}
}
Third step
Now we can declare as much directive classes as possible. With inheritance. And we can set up injections in simple way with spread arrays (just dont forget call super method). See examples:
class DirectiveFirst extends Directive {
}
DirectiveFirst.inject('injA', 'injB', 'injC');
class DirectiveSecond extends DirectiveFirst {
constructor(injD, ...injected) {
super(...injected);
this.otherInjectedProperty = injD;
}
}
// See appended injection does not hurt the ancestor class
DirectiveSecond.inject('injD');
class DirectiveThird extends DirectiveSecond {
constructor(...injected) {
// Do not forget call the super method in overridden constructors
super(...injected);
}
}
The last step
Now register directives with angular in simple way:
angular.directive('directiveFirst', DirectiveFirst.factory());
angular.directive('directiveSecond', DirectiveSecond.factory());
angular.directive('directiveThird', DirectiveThird.factory());
Now test the code:
var factoryFirst = DirectiveFirst.factory();
var factorySec = DirectiveSecond.factory();
var factoryThird = DirectiveThird.factory();
var directive = factoryFirst('A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));
directive = factorySec('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));
directive = factoryThird('D', 'A', 'B', 'C');
console.log(directive.constructor.name + ' ' + JSON.stringify(directive));
This will return:
DirectiveFirst {"injA":"A","injB":"B","injC":"C"}
DirectiveSecond {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}
DirectiveThird {"injA":"A","injB":"B","injC":"C","otherInjectedProperty":"D"}
class ToggleShortcut{
constructor($timeout, authService, $compile, $state){
var initDomEvents = function ($element, $scope) {
var shortcut_dropdown = $('#shortcut');
$compile(shortcut_dropdown)($scope);
$scope.goToShortCutItem = function(state, params){
var p = params || null;
if(state === 'app.contacts.view'){
var authProfile = authService.profile;
if(authProfile){
p = {
id:authProfile.user_metadata.contact_id
};
}
}
$state.go(state, p);
window.setTimeout(shortcut_buttons_hide, 300);
};
$element.on('click', function () {
if (shortcut_dropdown.is(":visible")) {
shortcut_buttons_hide();
} else {
shortcut_buttons_show();
}
});
// SHORTCUT buttons goes away if mouse is clicked outside of the area
$(document).mouseup(function (e) {
if (shortcut_dropdown && !shortcut_dropdown.is(e.target) && shortcut_dropdown.has(e.target).length === 0) {
shortcut_buttons_hide();
}
});
// SHORTCUT ANIMATE HIDE
function shortcut_buttons_hide() {
shortcut_dropdown.animate({
height: "hide"
}, 300, "easeOutCirc");
$('body').removeClass('shortcut-on');
}
// SHORTCUT ANIMATE SHOW
function shortcut_buttons_show() {
shortcut_dropdown.animate({
height: "show"
}, 200, "easeOutCirc");
$('body').addClass('shortcut-on');
}
};
var link = function($scope, $element){
$timeout(function(){
initDomEvents($element, $scope);
});
};
this.restrict = 'EA';
this.link = link;
}
}
toggleShortcut.$inject = ['$timeout', 'authService', '$compile', '$state'];
function toggleShortcut($timeout, authService, $compile, $state){
return new ToggleShortcut($timeout, authService, $compile, $state);
}
angular.module('app.layout').directive('toggleShortcut', toggleShortcut);
I had a similar problem. But in my case it worked and failed when I deployed to production. And it failed because production has the latest version of 6to5.
This could be prevented by using npm shrinkwrap.
According to the latest ES6 spec you can't use a class like this. https://github.com/babel/babel/issues/700
I faced the same problem. First time I tried to solve problem via ES6 classes but I have problem with $inject my dependencies. After I realized what angular have couple styles of writing code and I tried. At all I used John Papa styles and I got this works code in my rails app with ES6:
((angular) => {
'use strict';
var Flash = ($timeout) => {
return {
restrict: 'E',
scope: {
messages: '=messages'
},
template: (() => {
return "<div class='alert flash-{{ message[0] }}' ng-repeat = 'message in messages'>" +
"<div class= 'close' ng-click = 'closeMessage($index)' data-dismiss = 'alert' > × </div>" +
"<span class= 'message' >{{ message[1] }}</ span>" +
"</ div>";
}),
link: (scope) => {
scope.closeMessage = (index) => {
scope.messages.splice(index, 1)
};
$timeout(() => {
scope.messages = []
}, 5000);
}
}
};
Flash.$inject = ['$timeout'];
angular.module('Application').directive('ngFlash', Flash);
})(window.angular);
I know that I can do little bit improvements with functions and variables in more ES6 style.
I hope it helps.
I ran into this problem just now and I saw this topic. Tried some methods provided in discussion, I finally solved this problem in a very simple way:
export default function archiveTreeDirective() {
'ngInject';
return {
restrict: 'E',
scope: {
selectedNodes: "="
},
templateUrl: 'app/components/directives/archiveTree/archiveTree.html',
controller: ArchiveTreeController,
controllerAs: 'vm',
bindToController: true
};
}
class ArchiveTreeController {
constructor() {
'ngInject';
...
}
...
}
I directly use function as the .directive('directiveName',factory) argument, and export it, later import it in module declaration. But I missed the "default" statement when exporting, so I got an error. After I add "default" key word, everything works!
I find this method also works in my route configs (also in a function way).
============
Hope you can understand my poor English :)
following AngularJS in 60 minutes I'm trying to add factory to current code.
I have lineman angular app where angular is declared as follows:
angular.module("app", ["ngResource", "ngRoute"]).run(function($rootScope) {
// adds some basic utilities to the $rootScope for debugging purposes
$rootScope.log = function(thing) {
console.log(thing);
};
});
I want to add the following code but running into JS syntax issue
.factory('simpleFactory', function () {
var factory = {};
var customers = [];
factory.getCustomers = function () {
return customers;
};
return factory;
}
What's the right syntax to merge these 2 blocks? Also should I do mimic controllers directory to create factory or should I really add to the first block? Thanks
Technically you have already merged a certain block from another:
angular.module("app", ["ngResource", "ngRoute"])
.run(function($rootScope) {
// adds some basic utilities to the $rootScope for debugging purposes
$rootScope.log = function(thing) {
console.log(thing);
};
});
for your chain to continue invoking another method such that the "second block" you are talking about(technically its the third block right now), do not terminate the method invocation then simply remove the terminator ; and append the third block.
It must look like this:
angular.module("app", ["ngResource", "ngRoute"]).run(function($rootScope) {
// adds some basic utilities to the $rootScope for debugging purposes
$rootScope.log = function(thing) {
console.log(thing);
};
})
.factory('simpleFactory', function () {
var factory = {};
var customers = [];
factory.getCustomers = function () {
return customers;
};
return factory;
});
Note: Your third method invocation factory() was not closed properly, it lacks the closing parenthesis ) and the terminator symbol ;.
Make sure you chain the factory to your variable. It seems you broke your chain right now.