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 :)
Related
Stack:
Typescript 1.7 + Angular 1.49
Summary:
I have a directive. I want to $inject angular's $timeout service. It works fine in the directive's controller function, but not in the link function. What am I missing?
Questions:
What did I do wrong?
Is there a better way to $inject the $timeout dependency?
Why will the $timeout service work in the directive's controller but not the link?
MyDirective.ts:
module app.directives {
export class MyDirective {
priority = 0;
restrict = 'E';
templateUrl = 'template.html';
scope = {
'items': '='
};
controller = MyController;
link = MyLink;
static $inject = ['$timeout'];
constructor(private $timeout:ng.ITimeoutService) {
}
}
function MyController($scope:ng.IScope, $timeout:ng.ITimeoutService) {
console.log("controller", $timeout); // function timeout(fn,delay,invokeApply){ the guts here }
$timeout(function () {
console.log("This works fine");
},3000);
}
function MyLink(scope:ng.IScope, element:ng.IAugmentedJQuery, attr:ng.IAttributes, $timeout:ng.ITimeoutService) {
console.log("link to", $timeout); // MyController {}
$timeout(function () {
console.log("This throws the error, TypeError: $timeout is not a function");
},3000);
}
}
Connecting it in directives.ts:
module app.directives {
angular.module('app').directive('MyDirective',['$timeout',($timeout:ng.ITimeoutService) => new MyDirective($timeout) ]);
}
app.ts
module app {
angular.module('app', []);
}
What hasn't worked:
Using this.$timeout in MyLink, with or without including $timeout in the parameters.
I've found several articles and examples that I've tried to make sure I'm following the logic of in my app but can't seem to get it.
Final notes
Typescript-Angular is still new and there are so many best practices that are far from being defined. Part of my team's project is finding some of those.
We have been working on this general structure for a while, so unless there's a compelling reason, please refrain from suggestions that I change the structure of everything too much.
Link functions are not executed directly from directive instance, hence you wont get this as Directive's config instance (which you instantiate via new operator). Also you cannot inject anything to the link function (that is what directive constructor is for) unlike the controller constructor, the arguments to the link function are automatically passed in by the directive execution logic. You could use an arrow operator to resolve this issue.
Example:
export class MyDirective {
priority = 0;
restrict = 'E';
templateUrl = 'template.html';
scope = {
'items': '='
};
controller = MyController;
link:ng.IDirectiveLinkFn = (scope:ng.IScope, element:ng.IAugmentedJQuery, attr:ng.IAttributes) => {
//Here
this.$timeout(function () {
},3000);
};
constructor(private $timeout:ng.ITimeoutService) {
}
}
Or you could bind the context using function.bind. i.e link = MyLink; and access the $timeout using this.$timeout.
If interseted you could take a look at creating some syntactic sugars by using experimental decorators for directives or you could try exploring something like this. However (just my opinion) using a class for directive config seems to be an overkill, you might as well just use a function with static inject.
Link function 4th parameter is the controller instance him self.
If you want to do it you should do something like:
module app.directives {
export class MyDirective {
link = MyLink;
static $inject = ['$timeout'];
constructor(public $timeout:ng.ITimeoutService) {
}
}
function MyLink(scope:ng.IScope, element:ng.IAugmentedJQuery, attr:ng.IAttributes, ctrl:any) {
ctrl.$timeout(function () {
console.log("This throws the error, TypeError: $timeout is not a function");
},3000);
}
}
I know that this is not elegant but I have hard time finding a better solution, what do you think?
I have a factory where I have a couple of predefined partners (it could be anything else, I thought it's an example that's easy to understand). On run time, we select the current partner (based on some logic I omitted here).
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var partners = {
firstPartner: {
name: 'Default Partner',
id: 1 // just an extra property as example
},
secondPartner: {
name: 'Other Partner',
id: 2
}
};
// set default value
var partner = partners.firstPartner;
var initPartner = function () {
// based on some logic (omitted), select partner
partner = partners.secondPartner;
$log.log("initPartner should have changed partner to " + partner.name);
};
return {
initPartner: initPartner,
partners: partners,
partner: partner,
};
});
Then, I would like to access the partner as PartnersService.partner and see as it changes, e.g. from a controller:
angular.module('myApp').controller('myController',
function ($scope, $log, PartnersService) {
// PartnersService.partner is the default partner (firstPartner)
PartnersService.initPartner();
// After initPartner, PartnersService.partner is still
// the default, but I expected it to change
});
I found some workarounds (in my opinion... are they workarounds?), but it feels unnatural for me, so I'd like to ask if there's a better way.
See my full, working example on JS Bin. I apologize if you find the example a bit lengthy, but I wanted to make sure Stack Overflow users understand my concerns and can point out if something is wrong with the way I think.
Workaround 1 (getter?):
angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
PartnersService.initPartner();
var partner = PartnersService.getPartner();
$log.log('I could use a getter: ' + partner.name);
});
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var getPartner = function () {
return partner;
};
return {
getPartner: getPartner,
// ...
};
});
Workaround 2 (nest in an object literal):
angular.module('myApp').controller('myController', function ($scope, $log, PartnersService) {
PartnersService.initPartner();
$log.log('or nest the partner in an extra object literal: '
+ PartnersService.extraObjectLiteral.partner.name);
});
angular.module('myApp').factory('PartnersService', function ($location, $log) {
var partners = { /*...*/ }
var extraObjectLiteral = {
partner: partners.firstPartner
};
var initPartner = function () {
// based on some logic (omitted), select partner
extraObjectLiteral.partner = partners.secondPartner;
};
return {
extraObjectLiteral: extraObjectLiteral,
//...
};
});
Change to:
this.partner = partners.secondPartner;
in your initPartner method. That will solve it.
What you're really doing when you do
var partners = { ... }
var partner = partners.firstPartner;
is, you're creating local objects in the class, but they are not exposed members of the class. And with the
return {
initPartner: initPartner,
partners: partners,
partner: partner
};
you create members of the class, and copy the values of the local variables to the class' members. In your initPartner method, you change the local object, but the class' object remain unchanged.
I would go with the approach of using getters and setters as this follows the Revealing Module Pattern which is really preferred for maintaining cleaner, more readable and understandable code.
Factory code is as follows.
(function () {
angular.module('myApp').
factory('PartnersService', PartnersService);
function PartnersService($location, $log) {
var partners = {
firstPartner: {
name: 'Default Partner',
id: 1 // just an extra property as example
},
secondPartner: {
name: 'Other Partner',
id: 2
}
};
var partner;
//Set default value
setDefaultPartner();
var service = {
getPartner: getPartner,
setPartner: setPartner
};
return service;
/*** Function Declarations ***/
/* Accessible Functions */
function getPartner() {
return partner;
}
function setPartner() {
var selectedPartner = initPartner();
return selectedPartner;
}
/* Private Functions */
function initPartner() {
/*
* Some (omitted) logic to select partner
*/
partner = partners.secondPartner;
$log.log("initPartner should have changed partner to " + partner.name);
return partner;
}
function setDefaultPartner() {
partner = partners.firstPartner;
}
}
})();
Notice that the only public/accessible members left are the getPartner and setPartner (which calls initPartner) functions.
The controller would be the following.
(function () {
angular.module('myApp').
controller('myController', myController);
function myController($scope, $log, PartnersService) {
$scope.partner = PartnersService.getPartner();
$log.log("should be default partner: " + $scope.partner.name);
$scope.partner = PartnersService.setPartner();
$log.log("After setPartner which calls initPartner, the current partner is now the " + $scope.partner.name);
}
})();
Modifying your JS Bin, the following is the resulting console log.
"should be default partner: Default Partner"
"initPartner should have changed partner to Other Partner"
"After setPartner which calls initPartner, the current partner is now the Other Partner"
I don't think you are missing anything obvious. Both of your workarounds seem ok to me, except that in the latter case I would probably use the following structure:
var partners = { ... };
var initPartner = function () {
retObj.partner = partners.secondPartner;
};
var retObj = {
initPartner: initPartner,
partners: partners,
partner: partners.firstPartner
};
return retObj;
i'm using Angular directives like this:
'use strict';
var eventDirective = {
/**
* Initialize event directive and return the link
* calling all nested methods.
*
*/
init: function($scope, $element) {
var that = this;
return {
link: function(scope) {
scope.$watch('events', function() {
if (scope.events === undefined) {
return;
}
/**
* Every time the user access the event page, this methods
* will be called.
*
*/
__TableSorter__.init($element);
});
},
restrict: 'E'
};
},
__TableSorter__: {
init: function(element) {
console.log(element) // PRINTS ELEMENT
}
}
};
angular
.module('adminApp')
.directive('eventDirective', eventDirective.init.bind(eventDirective));
To illustrate I created this simple example. The TableSorter will run normally.
The problem is when I have several scripts, the code is too large. Is there any way to solve this? Maybe put scripts elsewhere as factories or services ?
My question is how to do this. I tried to inject a service within the directive but was resulting in undefined.
Thanks.
A good way to do should be, when you define your directive, you can set bindToController to true and right your logic inside a controller class. You may inject your services to that controller.
For example.
myModule.directive('directiveName', function factory(injectables) {
var directiveDefinitionObject = {
template: '<div></div>',
scope: {},
controllerAs: 'yourControllerClass',
bindToController: true
};
return directiveDefinitionObject;
});
yourControllerClass is angular controller here.
I'm using a framework called Radiant UI, which is a way to get HTML5 UI into Unreal Engine 4. I'm trying to pick up some modern Javascript while I do that, so I'm building the UI in AngularJS.
My understanding of Angular is still pretty weak though, and I'm a bit confused about what the best practice is here. The extension injects the following Javascript when it sets up.
var RadiantUI;
if (!RadiantUI)
RadiantUI = {};
(function() {
RadiantUI.TriggerEvent = function() {
native function TriggerEvent();
return TriggerEvent(Array.prototype.slice.call(arguments));
};
RadiantUI.SetCallback = function(name, callback) {
native function SetHook();
return SetHook(name, callback);
};
RadiantUI.RemoveCallback = function(name) {
native function RemoveHook();
return RemoveHook(name);
};
})();;
So this is simply pushing RadiantUI into the global namespace. That would be fine if the extension was always there, but it isn't. In the test environment (Chrome), it's not there. It's only there when running in the game engine. That, combined with the fact that globals suck, means I want to encapsulate it.
In the previous iteration of this, I had it wrapped in an AMD module, and it worked well. Like this:
define([], function()
{
if ("RadiantUI" in window)
{
console.log("RadiantUI in global scope already!");
return window.RadiantUI;
}
var RadiantUI;
if (!RadiantUI) {
RadiantUI = {};
RadiantUI.TriggerEvent = function() {}
RadiantUI.SetCallback = function() {}
RadiantUI.RemoveCallback = function() {}
}
console.log("Using fake RadiantUI bindings");
return RadiantUI;
});
So here's what I want to do:
I want to include radiant as a dependency to my app/stateProvider and have it injected, much the same way it would be in AMD. With the stub methods in place if the extension isn't present. What's the proper approach to this? A module? A service provider?
UPDATE: This is the working code using the answer given.
var myapp = angular.module('bsgcProtoApp', ['ui.router' ]);
myapp.value('radiant', window.RadiantUI || {
TriggerEvent: function()
{
console.log("TriggerEvent called");
},
SetCallback: function(name, callback)
{
console.log("Setcallback called");
},
RemoveCallback: function(name)
{
console.log("RemoveCallback called");
}
});
myapp.config(['$stateProvider', '$urlRouterProvider', function($stateProvider, $urlRouterProvider )
{
$urlRouterProvider.otherwise("/mainmenu");
$stateProvider.state('mainmenu',
{
name: "mainmenu",
url: "/mainmenu",
templateUrl: 'templates/mainmenu.html',
controller: ['$scope', 'radiant', function($scope, radiant)
{
$scope.tester = function()
{
radiant.TriggerEvent("DuderDude");
console.log("Duder!");
}
}],
});
}]);
You presumably have an Angular module or app. For the sake of this answer, let's call it MyApp.
Now you can do
MyApp.value("RadiantUI", window.RadiantUI || {
TriggerEvent = function(){},
//... more properties
});
Now to access this value as a dependency in a controller for example, you'd do this
MyApp.controller(["$scope", "RadiantUI", function($scope, RadiantUI){
// ... controller code ...
}]);
I have a situation where I want to create custom component, which should be reusable and provide public API to change it's state. I am trying to achieve this by building component using directive and controller.
What I desire to do is simply:
customComponent.apiMethod1( Math.floor( Math.random() * 2 ) );
Here is JSFiddle which should explain my case: http://jsfiddle.net/7d7ad/4/
On line 9 ( when user clicks a button ), I want to call line 22 method ( custom component public API method ). Is there anyway to achieve this?
You are looking for Providers. There are three different types: Factories, Services, and Providers. Each is a bit different you can take a look at this summary.
Providers can allow you to share common methods, functions and data between different areas of your application without duplicating code.
Short example - Fiddle
html
<div ng-app="myApp" ng-controller="testController">
<button ng-click="ClickMe()">Random</button>
{{display.value}}
</div>
javascript
angular.module('myApp', [])
.controller('testController', ['$scope','myService', function($scope, myService) {
$scope.display =new myService();
$scope.ClickMe = function() {
$scope.display.apiMethod1();
};
}])
.factory('myService', function() {
function factory() {
this.value = "Hello World";
this.apiMethod1 = function() {
this.value = Math.floor( Math.random() * 2 );
};
}
return factory;
});
You can, in addition to a service, use a parent directive with a controller.
Here is an example of how this might work (service example at the bottom):
app.directive('parentDir', function() {
return {
controller: function($scope, $element) {
var childFuns = [];
this.registerFun = function(func) {
childFuns.push(func);
}
//we will call this using ng-click
$scope.onClick = function(){
childFuns.forEach(function(func){
func.call(null,5)
});
}
}
}
})
And in the child directive:
app.directive('customcomp', function() {
return {
restrict: 'E',
scope: {},
require: '^parentDir', //we "require" the parent directive's controller,
//which makes angular send it as the fourth
//argument to the linking function.
template: '<h2>{{_currentNumber}}</h2>',
link: function(scope, elm, attrs, ctrl) {
scope._currentNumber = 0;
scope.apiMethod1 = function(val) {
scope._currentNumber = val;
};
//call the parent controller's registring function with the function
ctrl.registerFun(scope.apiMethod1);
}
}
});
Each child directive would "register" a function, and those functions can be stored and called from the parent directive in any way you want.
Note that you should use ng-click for events with angular.
FIDDLE
And here is how it might look with a service:
app.service('funcs', function(){
var funcs = [];
this.register = function(func){ funcs.push(func)};
this.call = function(){
funcs.forEach(function(func){
func.call(null,5);
})
}
})
FIDDLE