I am writing an app in AngularJs using Typescript and I am having trouble accessing a method in a directive from the controller class. I added 'control: '=', to the scope of the directive thinking this would bind the control of the methods in the directive to the controller but I am getting an error
The controller code is:
import {DaypartingSegmentsContainer} from "lib/js/publisher/deal/objects/dayparting/dayparting.segments.container";
#Component({
bindings: {
deal: "="
},
controllerAs: "$ctrl",
module: DealApp,
require: {
DealForm: '^form',
},
selector: 'deal-edit-dayparting',
template: require('./templates/deal.edit.dayparting.html'),
})
#Inject(
"$scope",
"$q"
)
export class DealEditDaypartingCtrl extends SxControllerBase {
public daypartingSegmentsContainer : DaypartingSegmentsContainer;
constructor(protected $scope : ng.IScope,
private $q : angular.IQService) {
super($scope);
}
public $onInit() : void {
let self : DealEditDaypartingCtrl = this;
this.daypartingSegmentsContainer.getCommonTimes();
}
}
The directive class is:
import {Inject, Directive} from "lib/js/angularjs/decorators/sx-forward";
import DealApp from "lib/js/publisher/deal/lib.deal.app";
import DealEditDaypartingCtrl from "lib/js/publisher/deal/edit/deal.edit.dayparting";
import "lib/js/publisher/deal/objects/dayparting/dayparting.jquery.factory";
import {DaypartingDayName} from "lib/js/publisher/deal/objects/dayparting/dayparting.day.name";
import {
minutesToString,
stringToMinutes
} from "lib/js/publisher/deal/objects/dayparting/dayparting.time.functions";
#Directive({
selector: 'dayparting-segments-container',
require: '^dealEditDayparting',
replace: true,
module: DealApp,
scope: {
dayName: '<',
control: '=',
},
template: require('./templates/dayparting.segments.container.html'),
controllerAs: '$ctrl',
link: function(scope : any, element : any, attrs : any, daypartingCtrl : DealEditDaypartingCtrl) : void {
scope.daypartingCtrl = daypartingCtrl;
// Delete button watch
scope.$watch(
() => {
return daypartingCtrl.deleteSegment;
},
(shouldDelete : boolean) => {
if (shouldDelete === true) {
scope.$ctrl.onDelete();
}
}
);
// Calendar leave watch
scope.$watch(
() => {
return daypartingCtrl.calendarLeave;
},
(calendarLeave : boolean) => {
if (calendarLeave === true) {
scope.$ctrl.onCalendarMouseLeave();
}
}
);
},
})
#Inject('$scope', 'JQueryFactory' )
export class DaypartingSegmentsContainer {
constructor(private $scope : any,
private JQueryFactory : any) {
this.tooltipIsOpen = false;
this.jquery = JQueryFactory.get();
}
public getCommonTimes() : void {
console.log("i am in getCommon times!!!")
}
}
export default DaypartingSegmentsContainer;
I keep getting a console error saying - Cannot read property 'getCommonTimes' of undefined
The code was staring at me the full time. I added the below piece of code and its now working:
scope.$watch(
() => {
return daypartingCtrl.daypartingOninit;
},
(daypartingOninit : boolean) => {
if (daypartingOninit === true) {
scope.$ctrl.getCommonTimes();
}
}
);
Related
I have a custom directive:
export class XHideDirective {
static $inject = ["$rootScope"];
static $rootScope: any;
public static build($rootScope) {
var directive: ng.IDirective = {
link: (scope, element, attributes: any) => {
var itemToHide = attributes["xHide"];
$rootScope.$on("$stateChangeStart",
(event, toState) => {
if (toState.data && toState.data.hasOwnProperty(itemToHide)) {
element.hide();
} else {
element.show();
}
});
}
};
return directive;
}
}
And what that does, is when a state has it, it'll hide all elements on the page with that directive set to that value.
.state("deposit.x.successful", {
url: "/successful/:transactionId",
controller: "DepositResultController",
templateUrl: "deposit/templates/x/successful.html",
data: { hideDepositMenu: null }
})
.state("deposit.x.pending", {
url: "/pending",
templateUrl: "deposit/templates/x/pending.html",
data: { hideDepositMenu: null }
})
.state("deposit.x.rejected", {
url: "/rejected",
templateUrl: "deposit/templates/x/rejected.html",
data: { hideDepositMenu: null }
This all works very well except in the case when I don't transition to that page naturally but I get forwarded there (either a server redirect) or if I refresh the page with Ctrl+F5. Then the "stateChangeStart" event doesn't get hit.
The directive is registered like this:
module Utils {
angular.module("Utils", [])
.directive("xHide", ["$rootScope", (r) => { return XHideDirective.build(r); }]);
}
How do I get the state in those cases?
I found this very similar issue with no solution
$stateChangeStart don't work when user refresh browser
Did you try to put this listener in a .run section ?
$rootScope.$on("$stateChangeStart",
(event, toState) => {
if (toState.data && toState.data.hasOwnProperty(itemToHide)) {
element.hide();
} else {
element.show();
}
});
I think I solved it by doing this:
export class XHideDirective {
static $inject = ["$rootScope", "$timeout"];
static $rootScope: any;
public static build($rootScope, $timeout) {
var directive: ng.IDirective = {
controller: DepositController,
link: (scope, element, attributes: any) => {
var itemToHide = attributes["xHide"];
$timeout(() => {
if (scope.hideMenu && scope.hideMenu.hasOwnProperty(itemToHide)) {
element.hide();
} else {
element.show();
}
}, 0);
$rootScope.$on("$stateChangeStart",
(event, toState) => {
if (toState.data && toState.data.hasOwnProperty(itemToHide)) {
element.hide();
} else {
element.show();
}
});
}
};
return directive;
}
}
and then inside the controller:
module Deposit {
export class DepositController extends Utils.BaseController {
constructor(public $state) {
this.$scope.hideMenu = this.$state.$current.data;
}
}
}
No idea if it's the optimal solution but it seems to work well so far.
Hope it helps someone.
I'm programming the followin angularjs directive in typescript for form validation. I want to figure out, how to use the directive's controller and the form inherit controller in the directive's link function.
TH's in advanced
module app.infrastructure.components.forms {
'use strict';
interface MyIFormController extends ng.IFormController {
$name: string;
}
interface IMskFormInputController {
setupDom: (element: any) => string;
addMessages: (form: ng.IFormController, element: ng.IAugmentedJQuery, name: string, scope: ng.IScope) => void;
updaterFor: (element: ng.IAugmentedJQuery) => any;
watcherFor: (form: ng.IFormController, name: string) => any;
}
class MskFormInputController implements IMskFormInputController {
static $inject = ['$compile'];
constructor(private $compile: ng.ICompileService) {
}
setupDom(element: any): string {
var name = null;
var input = element.querySelector("input, textarea, select, ui-select");
if (input !== undefined && input) {
name = input.getAttribute("name");
}
return name;
}
addMessages(form: any, element: ng.IAugmentedJQuery, name: string, scope: ng.IScope): void {
var messages = "<div class='help-block' ng-messages='" + form.$name + "." + name + ".$error" + "'>" +
"<div ng-messages-include='/app/infrastructure/directives/forms/messages.html'></div>" +
"</div>";
element.append(this.$compile(messages)(scope));
}
updaterFor(element: ng.IAugmentedJQuery): any {
return function (hasError) {
if (hasError) {
element.addClass("has-error");
}
else {
element.removeClass("has-error");
}
}
}
watcherFor(form: ng.IFormController, name: string): any {
return function () {
if (name && form[name]) {
return form[name].$invalid;
}
};
}
}
class MskFormInput implements ng.IDirective {
constructor() { }
static factory(): ng.IDirective {
return new MskFormInput;
}
controller = MskFormInputController;
controllerAs = 'mskFormInputController';
restrict = 'A';
require = ['^form'];
scope = {};
link(scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrl: any): void {
//var name = form.setupDom(element[0]);
//this.controller.addMessages(form[0], element, name, scope);
//scope.$watch(this.controller.watcherFor(form[0], name), this.controller.updaterFor(element));
}
}
angular
.module('app.infrastructure.components.forms.mskFormInputDrtvmdl')
.directive('mskFormInput', MskFormInput.factory);
}
Let's start: I believe that this question must be tagged with [angularjs].
First: communication between directive and controller should be through the scope.
Second: avoid DOM manipulate in the controller, do this in directives.
Not see any need for a controller in this code, since their responsibility is to manipulate the data and scope behavior.
Look for studying the fundamentals of angularjs, learn responsibility and differences between each small part of that heroic framework View more here, and here. And to know write better angularjs with typescript code, watch this
Here follows the refactored code to these rules:
module app.infrastructure.components.forms {
'use strict';
MskFormInput.$inject = ['$compile']
function MskFormInput($compile: ng.ICompileService): ng.IDirective {
var setupDOM = (element: HTMLElement) => {
var name = null;
var input = element.querySelector("input, textarea, select, ui-select");
if (input !== undefined && input) {
name = input.getAttribute("name");
}
return name;
};
var addMessages = (element: ng.IAugmentedJQuery, form: any, name: string, scope) => {
var messages =
`<div class="help-block" ng-messages="${form.$name}.${name}.$error">
<div ng-messages-include='/app/infrastructure/directives/forms/messages.html'></div>
</div>`;
element.append(this.$compile(messages)(scope));
};
var invalidForm = (form: ng.IFormController, name: string) => {
return function() {
if (name && form[name]) {
return form[name].$invalid;
}
};
};
return {
restrict: 'A',
require: ['^form'],
scope: {},
link: (scope: ng.IScope, element: ng.IAugmentedJQuery, attrs: ng.IAttributes, ctrl: any) => {
var name = setupDOM(m);
var form = element[0].form;
if (!form)
return;
addMessages(form, element, name, scope);
scope.$watch(invalidForm(form, name), (hasError) =>
element.toggleClass('has-error', hasError)
);
}
};
}
angular
.module('app.infrastructure.components.forms.mskFormInputDrtvmdl')
.directive('mskFormInput', MskFormInput);
}
I have written custom angular element directive, which shows/hides loading indicator based on condition evaluated on service call. The directive is applied as element to element. The directive works fine, but the problem the content is of the applied element not hidding.
Here is my directive:
export interface ILoadingIndicatorAttr extends ng.IAttributes {
efpLoadingIndicator: string;
}
export interface ILoadingIndicatorscope extends ng.IScope {
loading: boolean;
}
export class LoadingIndicator implements ng.IDirective {
public restrict: string = 'A';
public replace: boolean = true;
static $inject = ["$compile", "$templateRequest", "$timeout"];
constructor(public _compile: ng.ICompileService, private templateService: ng.ITemplateRequestService, private timeout: ng.ITimeoutService) {
};
link = (scope: ILoadingIndicatorscope, element: ng.IAugmentedJQuery, attrs: ILoadingIndicatorAttr, ngModel: ng.INgModelController): void => {
var tempThis = this;
var templateUrl: string = 'app/modules/common/directives/loadingIndicator/loadingIndicator.tmpl.html';
attrs.$observe('efpLoadingIndicator', () => {
if (attrs.efpLoadingIndicator == "true") {
this.timeout(() => {
if (attrs.efpLoadingIndicator == "true") {
scope.loading = true;
tempThis.resolveTemplate(templateUrl).then((html: ng.IAugmentedJQuery) => {
tempThis._compile(html)(scope).appendTo(element);
});
}
else {
scope.loading = false;
}
}, 1000);
}
else {
scope.loading = false;
}
});
}
resolveTemplate = (templateUrl: string): ng.IPromise<ng.IAugmentedJQuery> => {
return this.templateService(templateUrl).then((html: string) => {
return angular.element(html);
});
}
}
Here is how it is applied:
<div efp-loading-indicator ={{vm.isLoading}}>
<select> </select>
</div
I want to hide the content of div when loading indicator is present, show when loading indicator is off. I know i can use ng-show on child elements but don't want to do that since I want to reuse this directive.
The problem is that this is not what you expect in the link function. At that point,this will be referring to the window object (and thus tempThis too).
If you need to use the link function, you have to rebind it to this in the constructor, where this is referring to the class:
export interface ILoadingIndicatorAttr extends ng.IAttributes {
efpLoadingIndicator: string;
}
export interface ILoadingIndicatorscope extends ng.IScope {
loading: boolean;
}
export class LoadingIndicator implements ng.IDirective {
public restrict: string = 'A';
public replace: boolean = true;
link:(scope: ILoadingIndicatorscope, element: ng.IAugmentedJQuery, attrs: ILoadingIndicatorAttr, ngModel: ng.INgModelController)=> void
static $inject = ["$compile", "$templateRequest", "$timeout"];
constructor(public _compile: ng.ICompileService, private templateService: ng.ITemplateRequestService, private timeout: ng.ITimeoutService) {
this.link = this.myLink.bind(this);
};
myLink(scope: ILoadingIndicatorscope, element: ng.IAugmentedJQuery, attrs: ILoadingIndicatorAttr, ngModel: ng.INgModelController): void => {
var tempThis = this;
var templateUrl: string = 'app/modules/common/directives/loadingIndicator/loadingIndicator.tmpl.html';
attrs.$observe('efpLoadingIndicator', () => {
if (attrs.efpLoadingIndicator == "true") {
this.timeout(() => {
if (attrs.efpLoadingIndicator == "true") {
scope.loading = true;
tempThis.resolveTemplate(templateUrl).then((html: ng.IAugmentedJQuery) => {
tempThis._compile(html)(scope).appendTo(element);
});
}
else {
scope.loading = false;
}
}, 1000);
}
else {
scope.loading = false;
}
});
}
resolveTemplate = (templateUrl: string): ng.IPromise<ng.IAugmentedJQuery> =>{
return this.templateService(templateUrl).then((html: string) => {
return angular.element(html);
});
}
}
I have some issue with declaring a class. I have the following code:
module Directive.toast{
import ToastService = Toaster.ToasterService;
'use strict';
interface IToastScope extends ng.IScope {
message:string;
display:boolean
}
class ToastDirective implements ng.IDirective {
templateUrl= '/toast.html';
restrict= 'AE';
replace= true;
scope = {
};
public message:any;
public display:boolean;
constructor(public Toast:ToastService, public $timeout:angular.ITimeoutService) {
}
link:ng.IDirectiveLinkFn = (scope:IToastScope, element:ng.IAugmentedJQuery, attributes:ng.IAttributes) => {
scope.message = this.Toast.message.text;
this.$timeout(function() {
this.message.display= false;
element.remove();
}, 5000);
};
static factory():ng.IDirectiveFactory {
return () => new ToastDirective();
}
}
var app = AppModule.getModule();
app.directive('toast', ToastDirective.factory());
}
Which gives me an error:
error TS2346: Supplied parameters do not match any signature of call target.
However, if I delete the injections from the constructor, it will pass through the compiler - but I do need to inject those dependencies, to use them in the linking functions to the directive. Any ideas?
I would do something like this;
export var ToastDirective = (public Toast:ToastService, public $timeout:angular.ITimeoutService) : angular.IDirective =>
{
return {
templateUrl: '/toast.html',
restrict: 'AE',
replace: true,
scope: {
message: "=",
display: "="
},
link: (scope: IToastScope, element:angular.IAugmentedJQuery, attributes: angular.IAttributes) => {
scope.message = Toast.message.text;
$timeout(()=> {
scope.message.display=false;
element.remove();
}, 5000);
}
}
ToastDirective.$inject = ["ToastService", "$timeout"];
var app = AppModule.getModule();
app.directive('toast', ToastDirective);
}
I am using AngularJS and had been so for quite a while recently i started to mess around with Typescript i was able to convert most of my Angular code to Typescript and found great benefit especially when it get to services, but i can not convert the following directive to typescript class anyone has an idea how to do that, it works perfectly fine as AngularCode
angular.module('myValidation', [])
.directive('myValidation', function() {
return {
restrict: 'A',
require: "ngModel",
link: function(scope, elm, attrs, ctrl) {
switch (attrs.myValidation) {
case 'email':
var regex = /^[_a-z0-9]+(\.[_a-z0-9]+)*#[a-z0-9-]+(\.[a-z0-9-]+)*(\.[a-z]{2,4})$/;
break;
}
ctrl.$parsers.unshift(function(viewValue) {
if (regex.test(viewValue)) {
ctrl.$setValidity('myValidation', true);
}
else {
ctrl.$setValidity('myValidation', false);
}
return viewValue;
});
ctrl.$formatters.unshift(function(viewValue) {
if (regex.test(viewValue)) {
ctrl.$setValidity('myValidation', true);
}
else {
ctrl.$setValidity('myValidation', false);
}
return viewValue;
});
}
};
});
https://github.com/aleksey-pastuhov/AngularJS-Typed-Core
#directive(app)
export default class CopyText implements IDirective {
angularModule = app;
restrict = "E";
scope: any = {
model: "="
};
templateUrl = "Templates/Directives/copyText.html";
link: (scope: any, elm: any) => void = (scope, elm: any) => {
var input: JQuery;
scope.select = ($event) => {
input = $($event.target).next().first();
};
scope.copy = () => {
input.on("focus", function() {
this.setSelectionRange(0, this.value.length);
});
input.focus();
};
};
}
directive angularjs required a function. If you want to use directive with typescript class, you need to implement a function Factory for return instance of your class. Look this exemple:
Module Directive{
class myDirectiveCtrl {
private name: string;
private items: Array<string>;
private $q: ng.IQService;
static $inject = ['$scope','$window','$q'];
constructor($scope: ImyDirectiveScope, $window) {
this.name = $scope.name;
this.items = ['salut', 'hello', 'hi', 'good morning'];
}
clickMe = ():void => {
console.log('dssff');
this.items.push('yo');
}
}
export interface ImyDirectiveScope {
name: string
}
export class myDirective implements ng.IDirective {
restrict = 'E';
template = '<div><h1>{{vm.name}}</h1><div ng-repeat="i track by $index in vm.items">{{i}}</div><hr/><button type="button" ng-click="vm.clickMe()">Click Me</button></div>';
replace = true;
scope = {
name: '#'
};
controller = myDirectiveCtrl;
controllerAs = 'vm';
link = (scope: ng.IScope, element: ng.IAugmentedJQuery, attributes: ng.IAttributes, controller: myDirectiveCtrl) => {
};
constructor() { };
static factory(): ng.IDirectiveFactory {
var directive = () => new myDirective();
return directive;
}
}
app.directive('myDirective',Directive.myDirective.factory());
}