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);
});
}
}
Related
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 injected storageService but i tried to access in the link function using this.storageService its giving undefined.
Can any one help me on this ?
module App.Directive {
import Services = Core.Services;
import Shared = Core.Shared;
export class UserNav implements ng.IDirective {
restrict = 'A';
require: any = "^?ngModel";
scope = true;
templateUrl: any = "template/user-nav.html";
link: ng.IDirectiveLinkFn = function(scope, element, attributes, ngModel: any) {
var a = this.storageService.getItem("userInfo", false);
console.log("getting > " + a);
}
constructor(public routerService: Services.RouterService,
public storageService: Services.StorageService) {
}
static factory(): any {
var directive = (routerService: Services.RouterService,
storageService: Services.StorageService) => {
return new UserNav(routerService, storageService);
}
directive['$inject'] = ['routerService', 'storageService'];
return directive;
}
}
}
So the problem is that the link function
Change:
module App.Directive {
import Services = Core.Services;
import Shared = Core.Shared;
export class UserNav implements ng.IDirective {
restrict = 'A';
require: any = "^?ngModel";
scope = true;
templateUrl: any = "template/user-nav.html";
link: ng.IDirectiveLinkFn = function(scope, element, attributes, ngModel: any) {
var a = this.storageService.getItem("userInfo", false);
console.log("getting > " + a);
}
constructor(public routerService: Services.RouterService,
public storageService: Services.StorageService) {
}
static factory(): any {
var directive = (routerService: Services.RouterService,
storageService: Services.StorageService) => {
return new UserNav(routerService, storageService);
}
directive['$inject'] = ['routerService', 'storageService'];
return directive;
}
}
}
To:
module App.Directive {
import Services = Core.Services;
import Shared = Core.Shared;
export class UserNav implements ng.IDirective {
restrict = 'A';
require: any = "^?ngModel";
scope = true;
templateUrl: any = "template/user-nav.html";
link(scope, element, attributes, ngModel: any) {
var a = this.storageService.getItem("userInfo", false);
console.log("getting > " + a);
}
constructor(public routerService: Services.RouterService,
public storageService: Services.StorageService) {
}
static factory(): any {
var directive = (routerService: Services.RouterService,
storageService: Services.StorageService) => {
return new UserNav(routerService, storageService);
}
directive['$inject'] = ['routerService', 'storageService'];
return directive;
}
}
}
UPDATE: The above answer won't work. Because the link function is used as a callback and this is the global object. You'd have to set the services as a variable inside your module, then you'd have access to it through scope. see my fiddle
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());
}
I have a Typescript directive class and a controller class like below. I want to set a watch on isolated scope variable in the Typescript controller class. I am not able to access the isolated scope variables inside the controller class.
How can I do that?
My directive class:
module Sgm.Workspace.Web.Users {
"use strict";
export class UwAuthority implements ng.IDirective {
restrict = "E";
replace = false;
templateUrl = "/app/features/users/uwAuthority.html";
scope = {
showParameters: '=showParameters'
};
controller = UwAuthorityController;
//controllerAs = "vm";
link(scope: ng.IScope, element: ng.IAugmentedJQuery, attr: ng.IAttributes, controller: UserParametersController): void {
}
static instance(): ng.IDirective {
return new UwAuthority();
}
}
angular.module("workspaceApp")
.directive("showUwAuthority", UwAuthority.instance)
.controller("UwAuthorityController", ['userService', '$scope', (userService: Sgm.Workspace.Web.Services.IUserService, $scope: ng.IScope) => new UwAuthorityController(userService, $scope)]);
}
and my controller class is
module Sgm.Workspace.Web.Users {
export interface IUwAuthorityController {
loadFinancingType(): void;
}
export class UwAuthorityController implements IUwAuthorityController {
public modernBrowsers: any = [];
public outputBrowsers: any = [];
public localLang: any = [];
public showParameters: boolean;
static $inject = ['userService', '$scope'];
constructor(
private userService: Services.IUserService,
private $scope: ng.IScope) {
var vm = this;
var a = this.showParameters;
this.loadFinancingType();
this.$scope.$watch(() => this.showParameters, (oldValue: string, newValue: string) => {
console.log("showParameters")
});
this.$scope.$watch(() => this.outputBrowsers, (oldValue: string, newValue: string) => {
this.tellmeItChanged(oldValue, newValue);
});
}
public loadFinancingType = (): void => {
this.modernBrowsers = [{
name: "JUMBO 5/1 LIBOR ARM EVERBANK",
ticked: false
}];
this.localLang = {
selectAll: "All",
selectNone: "None",
reset: "Reset",
search: "Search...",
nothingSelected: "Select"
}
}
private tellmeItChanged(oldValue: string, newValue: string) {
if (oldValue !== newValue) {
if (this.outputBrowsers.length > 1) {
document.getElementById('plan').childNodes[0].childNodes[0].nodeValue = this.outputBrowsers.length + ' Selected';
}
}
}
}
}
We can create interface for our isolated scope and pass it into controller:
// the custom scope interface
export interface IUwAuthorityScope extends ng.IScope
{
showParameters: boolean;
}
// the controller interface
export interface IUwAuthorityController
{
loadFinancingType(): void;
}
export class UwAuthorityController implements IUwAuthorityController
{
// here we inform compiler, that injected '$scope' will be of
// our custom type - IUwAuthorityScope
constructor(
private userService: Services.IUserService,
private $scope: IUwAuthorityScope)
{
...
// here we can watch whatever we expect to be in the $scope
this.$scope.$watch("showParameters", showParms => {
// here we can access fully type $scope variables
if(this.$scope.showParameters === true) ...
});
}
...
I did follow your advise here but I keep having this error:
Error: [$injector:unpr] Unknown provider: IUwAuthorityScopeProvider <- $scope