Pass function to directive - angularjs

Here is my directive:
module app.directives {
interface IDmDeleteIconController {
doAction(): void;
}
class DmDeleteIconController implements IDmDeleteIconController {
static $inject = ['$scope'];
constructor(public $scope:any) {
}
doAction() {
console.log('1');
this.$scope.dmAction();
}
}
export class DmDeleteIcon implements ng.IDirective {
templateUrl = './app/common/directives/deleteIcon/deleteIcon.html';
controller = DmDeleteIconController;
controllerAs = 'dmDeleteIconVm';
scope = {
dmAction: '&'
};
link = (scope: any, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrl: any) => {
console.log('2');
scope.dmAction();
}
static factory(): ng.IDirectiveFactory {
const directive = () => new DmDeleteIcon();
directive.$inject = [];
return directive;
}
}
angular.module('myApp').directive('dmDeleteIcon', DmDeleteIcon.factory());
}
Here I am trying to use it:
dm-delete-icon(dm-action="console.log('hello');")
When I am open page I'll get 2 in the console (from link function), but I don't get the hello from the function I have passed to directive.
Why and how can I fix it?
Update:
Here is a directive template:
a.item-detail-delete-icon(class="form-image-link" href="" ng-click="dmDeleteIconVm.doAction()")
Here is HTML to which my Jade compile:
<dm-delete-icon dm-action="console.log('hello');"></dm-delete-icon>
Update 2:
I was trying to use it like this:
<dm-delete-icon dm-action="vm.foo()"></dm-delete-icon>
where:
foo(): void {
console.log("hello");
}
the function in the controller.
Update 3:
If I am trying to debug this code I will get this:

The problem here is that you're passing to the directive an expression console.log('hello'); that will be executed on parent controller scope.
This basically means you'd need to have console object attached to scope and under that object a method log. Angular expressions don't work with globals (console in this case) automatically.
Simply ensure that the expression you pass to the directive is a valid angular expression and that should work. For example - create new method on app scope called myConsoleLog that prints something to the console and change the attribute value for the directive to dm-action="myConsoleLog();"

Related

Find element not found in AngularJS

I have component in AngularJS and a template which looks something like the following
Template
<div id="{{ $ctrl.idName }}"></div>
And the component controller looks something like this
Component controller
export class SomeCtrl {
idName: string = "idName";
constructor(public $element) {
}
$postLink(): void {
const divElement = this.$element.find(`#${ this.idName }`);
console.log(divElement); //divElement is undefined
}
}
For some reason divElement becomes undefined, however if I write the name in the template as following
<div id="idName"></div>
Then the element is found. My assumption that it was due to template hasn't finished compiling. But it seems not because of that either since the $postLink() method is fired when compiling is finished according to the following article ThoughtRam - Exploring Angular 1.5: Lifecycle Hooks
Enclose the find in a $timeout:
export class SomeCtrl {
idName: string = "idName";
constructor(public $element, public $timeout) {
}
$postLink(): void {
this.$timeout( _ => {
const divElement = this.$element.find(`#${ this.idName }`);
console.log(divElement);
});
}
}
This will allow the AngularJS framework and the browser time to render the interpolation.

TypeScript + AngularJS : Directive returning 0 on scope.$eval

I created a directive to compile some html then bind it to my element.
Here is the directive
export class BindCompileHtmlDirective implements ng.IDirective {
restrict = 'A';
link = (scope: ng.IScope, element: any, attrs: any, ctrl: any) => {
scope.$watch((scope) => {
console.log(attrs);
//Also tried attrs.BindCompileHtmlDirective which returns undefined
//Tried attrs.$attr.bindCompileHtml also which returns 0 too
return scope.$eval(attrs.bindCompileHtml);
}, (value) => {
console.log(value);
// In case value is a TrustedValueHolderType, sometimes it
// needs to be explicitly called into a string in order to
// get the HTML string.
element.html(value && value.toString());
var $phe = this.$compile(element.contents())(scope);
//element.after($phe);
});
}
constructor(private $compile: any) {
}
static factory(): ng.IDirectiveFactory {
const directive = ($compile: any) => new BindCompileHtmlDirective($compile);
directive.$inject = ['$compile'];
return directive;
}
}
angular.module('rundeckManager.directives')
.directive('bindCompileHtml', BindCompileHtmlDirective.factory());
and here is the implementation :
<span id="id-of-span" bind-compile-html="{{TabsDomains.domainsLabelHtml}}"></span>
The attrs object looks good and contains the value of the string I need to compile, here is the attr object :
attrsObjectLog
But my directive returns 0 as a value instead of the compiled string and I don't understand why.
If someone can explain and help it'd be really helpful.
As said in the comments of the question, this only needs :
return attrs.bindCompileHtml
I'm pasting the answer in the comments here :
" $eval is for angular expressions, not HTML, maybe as an angular expression this HTML is evaled as 0? The bindCompileHtml content seems ready to be placed in the DOM as it is "

angularJS ES6 Directive

I am trying to develop an application in angular es6 . I have a problem with directve.
Here is my code
export default class RoleDirective {
constructor() {
this.template="";
this.restrict = 'A';
this.scope = {
role :"#rolePermission"
};
this.controller = RoleDirectiveController;
this.controllerAs = 'ctrl';
this.bindToController = true;
}
// Directive compile function
compile(element,attrs,ctrl) {
console.log("df",this)
}
// Directive link function
link(scope,element,attrs,ctrl) {
console.log("dsf",ctrl.role)
}
}
// Directive's controller
class RoleDirectiveController {
constructor () {
console.log(this.role)
//console.log("role", commonService.userModule().getUserPermission("change_corsmodel"));
//$($element[0]).css('visibility', 'hidden');
}
}
export default angular
.module('common.directive', [])
.directive('rolePermission',[() => new RoleDirective()]);
The problem is i couldn't get the role value inside constructor.
here is my html implementation
<a ui-sref="event" class="button text-uppercase button-md" role-permission="dfsd" detail="sdfdsfsfdssd">Create event</a>
If i console this it will get the controller object. But it will not get any result while use this.role.
Ok, so I managed to find out how this works.
Basically, the scope values cannot be initialized on the controller's constructor (because this is the first thing executed on a new object) and there is also binding to be considered.
There is a hook that you can implement in your controller that can help you with your use case: $onInit:
class RoleDirectiveController {
constructor () {
// data not available yet on 'this' - they couldn't be
}
$onInit() {
console.log(this.role)
}
}
This should work. Note that this is angular1.5+ way of doing things when not relying on $scope to hold the model anymore. Because if you use the scope, you could have it in the controller's constructor (injected).

Directive with Typescript and isolate scope function binding

I am writing a web app with AngularJS and, for the first time, using TypeScript. The issue I'm having is with a directive executing a function passed on its isolate scope.
I have a controller that is managing "work" of different types. I then have different directives to manage each type of work. The controller reserves the next piece of work, then places it on the scope for the appropriate directive. The controller is below.
module app.work {
interface IWorkScope {
workDone: boolean;
workCompleted(): void;
}
export class WorkController implements IWorkScope {
currentReservedWork: app.work.IReservedWork;
static $inject = ['app.work.WorkService', '$log'];
constructor(private workService: app.work.IWorkService,
private $log: ng.ILogService) {
}
private getNextWork() {
//...call service, reserve work, prep data...
}
public workCompleted(): void {
//...any cleanup tasks, reserve next piece of work....
}
}
angular.module('app.work')
.controller('app.work.WorkController', WorkController);
}
The directives then handling the actual workflow of executing the "work" of its type. An example is below.
module app.verify {
'use strict';
export interface IVerifyItemScope extends ng.IScope {
reserved: app.work.IReservedWork;
onItemComplete(): any;
saveButtonClicked(): void;
item: app.verify.IVerifyItem;
vm: app.verify.IVerifyItemController;
}
export interface IVerifyItemController {
getVerifyItem(): void;
}
class VerifyItemController implements IVerifyItemController{
item: app.verify.IVerifyItem;
statuses: app.verify.VerifyResultStatus[];
tempBind: number;
static $inject = ['$scope', 'app.verify.VerifyService', '$log'];
constructor(public $scope: IVerifyItemScope,
private verifyService: app.verify.IVerifyService,
private $log: ng.ILogService) {
}
saveButtonClicked(): void {
this.$log.debug('button clicked');
this.$scope.onItemComplete();
}
getVerifyItem(): void {
//...call service to get specific work details...
}
}
angular
.module('app.verify')
.directive('sfVerifyItem', verifyItem);
function verifyItem(): ng.IDirective {
var directive = <ng.IDirective> {
restrict: 'E',
link: link,
templateUrl: '....',
controller: VerifyItemController,
controllerAs: 'vm',
scope: {
reserved: '=',
onItemComplete: '&'
}
};
function link(scope: any, element: ng.IAugmentedJQuery, attributes: any, controller: IVerifyItemController): void {
//...do any prep...
}
return directive;
}
}
<data-sf-verify-item reserved="vm.currentReservedWork" onItemComplete="vm.workCompleted()"></data-sf-verify-item>
When the user compeletes the "work", the directive will do any necessary service calls. Last, it executes function onItemComplete that was passed on the scope to inform the controller that the work is done, then the controller can reserve more work and the process repeats.
The issue I'm having is the function bound on the scope isn't getting executed. I can debug and see it on the isolate scope, but when I execute it in the directive (saveButtonClicked() above), nothing happens in the parent controller.
Sorry for the long post, but any help or insight would be greatly appreciated.
See Hugues comment above, forgot to convert binding from camel case.

Implementing angularjs directives as classes in Typescript

So after taking a look at some of the examples of angularjs directives in typescript, it seems most people agree to use functions instead of classes when implementing them.
I would prefer to have them as a class and attempted to implement them as follows:
module directives
{
export class search implements ng.IDirective
{
public restrict: string;
public templateUrl: string;
constructor()
{
this.restrict = 'AE';
this.templateUrl = 'directives/search.html';
}
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Now this works fine. However, I need to have an isolated scope with some attributes and I'm struggling to find out how to include that in the class itself.
logic dictates that since I can have
public restrict: string;
public templateUrl: string;
I should be able to have something like:
public scope;
But I'm not sure if this is correct or how to carry on from there (i.e how to add the attributes to the scope).
Anybody know how to solve this? (hopefully, without having to revert to a function if possible)
Thanks,
Creating directives as classes can be problematic since you still need to involve a factory function to wrap its instantiation. For example:
export class SomeDirective implements ng.IDirective {
public link = () => {
}
constructor() {}
}
What Doesn't Work
myModule.directive('someDirective', SomeDirective);
Since directives are not invoked using 'new' but are just called as factory functions. This will cause problems on what your constructor function actually returns.
What Does (with Caveats)
myModule.directive(() => new SomeDirective());
This works fine provided you don't have any IoC involved, but once you start introducing injectables, you have to maintain duplicate parameter lists for your factory function and your directive contstructor.
export class SomeDirective implements ng.IDirective {
...
constructor(someService: any) {}
}
myModule.directive('someDirective', ['someService', (someService) => new SomeDirective(someService)]);
Still an option if that is what you prefer, but is important to understand how the directive registration is actually consumed.
An alternative approach
The thing that is actually expected by angular is a directive factory function, so something like:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return {
link: () => {...}
};
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
myModule.directive('someDirective', SomeDirectiveFactory);
This has the benefit of allowing the use of $inject annotations since angular needs it to be on the factory function in this case.
You could always return an instance of your class from the factory function as well:
export var SomeDirectiveFactory = (someService: any): ng.IDirective => {
return new SomeDirective(someService);
}
SomeDirectiveFactory.$inject = ['someService']; //including $inject annotations
But really depends on your use case, how much duplication of parameter lists you are okay with, etc.
Assuming that what you have works without an islolated scope, the following should work with an isolated scope:
module directives
{
export class search implements ng.IDirective
{
public restrict = 'AE';
public templateUrl = 'directives/search.html';
public scope = {
foo:'=',
bar:'#',
bas:'&'
};
public link($scope: ng.IScope, element: JQuery, attributes: ng.IAttributes)
{
element.text("Hello world");
}
}
}
Here is my proposal:
Directive:
import {directive} from '../../decorators/directive';
#directive('$location', '$rootScope')
export class StoryBoxDirective implements ng.IDirective {
public templateUrl:string = 'src/module/story/view/story-box.html';
public restrict:string = 'EA';
public scope:Object = {
story: '='
};
public link:Function = (scope:ng.IScope, element:ng.IAugmentedJQuery, attrs:ng.IAttributes):void => {
// console.info(scope, element, attrs, this.$location);
scope.$watch('test', () => {
return null;
});
};
constructor(private $location:ng.ILocationService, private $rootScope:ng.IScope) {
// console.log('Dependency injection', $location, $rootScope);
}
}
Module (registers directive...):
import {App} from '../../App';
import {StoryBoxDirective} from './../story/StoryBoxDirective';
import {StoryService} from './../story/StoryService';
const module:ng.IModule = App.module('app.story', []);
module.service('storyService', StoryService);
module.directive('storyBox', <any>StoryBoxDirective);
Decorator (adds inject and produce directive object):
export function directive(...values:string[]):any {
return (target:Function) => {
const directive:Function = (...args:any[]):Object => {
return ((classConstructor:Function, args:any[], ctor:any):Object => {
ctor.prototype = classConstructor.prototype;
const child:Object = new ctor;
const result:Object = classConstructor.apply(child, args);
return typeof result === 'object' ? result : child;
})(target, args, () => {
return null;
});
};
directive.$inject = values;
return directive;
};
}
I thinking about moving module.directive(...), module.service(...) to classes files e.g. StoryBoxDirective.ts but didn't make decision and refactor yet ;)
You can check full working example here: https://github.com/b091/ts-skeleton
Directive is here: https://github.com/b091/ts-skeleton/blob/master/src/module/story/StoryBoxDirective.ts
Here finally i got working a directive as class plus inheritance. In derived directive I extend the scope plus define the templateUrl.
You can override any methods from base directive
.
The key was to return from constructor the instance of directive.Angularjs calls constructor without new keyword. In this case this is of type window
I wrapped few lines to check the instance type of this and in case of window I create a new instance of directive. (See Activator class from sample below)
module Realty.directives {
export class BaseElementWithLabel implements ng.IDirective {
public restrict = 'E';
public replace = true;
public scope = {
label: '#',
model: '=',
icon: '#',
readonlyElement: '=',
remark: '#'
}
constructor(extendedScope) {
if (!(this instanceof Window)) {
extendedScope = extendedScope || {};
this.scope = angular.extend(this.scope, extendedScope);
}
}
link(scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller, transclude) {
scope['vm'] = {};
}
}
}
module Realty.directives {
export class textareaWithLabel extends Realty.directives.BaseElementWithLabel implements ng.IDirective {
templateUrl = 'directives/element-form/textarea-with-label-template.html';
constructor() {
super({
rows: '#'
});
return Realty.Activator.Activate(this, textareaWithLabel, arguments);
}
};
};
module Realty {
export class Activator {
public static Activate(instance: any, type, arguments) {
if (instance instanceof type) {
return instance;
}
return new(type.bind.apply(type, Array.prototype.concat.apply([null], arguments)));
}
}
}

Resources