How to choose the trigger order - angularjs

I have this directive:
module.directive("myDirective", function() {
return {
restrict: 'E',
templateUrl: 'pathToUrl',
controller: myCtrl,
controllerAs: vm,
bindToController: true,
scope: {
flag: '=',
toggleFlagFunc: '='
}
}
function myCtrl() {
let vm = this;
vm.olMap = new ol.Map({/*map init here*/})
vm.olMap.on("click", clickFunc);
function clickFunc() {
if (vm.flag) {
// Do some logic
vm.toggleFlagFunc();
}
}
}
}
This is the template url:
<div class="first-div"></div>
<div class="second-div"></div>
<div class="map-container-div"></div>
<div class="third-div"></div>
And this instance:
<my-directive flag="vm.flag" toogle-flag-func="vm.toogleFlagFunc" ng-click="vm.myDirectiveClicked()"></my-directive>
And this is the parent ctrl:
function parentCtrl {
let vm = this;
vm.flag = false; // The flag changed to true somewhere
vm.toggleFlagFunc = () => {
vm.flag = !vm.flag;
// Some Other Logic
}
vm.myDirectiveClicked = () => {
if (!vm.flag) {
// Do some logic here
}
}
}
My problem is when vm.flag evaluates to true and I click on the map-container-div, the order of the called functions is vm.toogleFlagFunc and then vm.myDirectiveClicked.
Can I change the order some how?
Thanks in advance!

Related

AngularJs call method in template directive

I have following directive that renders a number value like 3 as 3.xxx
depending on argument decimals
app.directive("myValue", () => {
return {
template: '{{value.toFixed(decimals)}}',
scope: {
'value': '=myValue',
'decimals': '=myValueDecimals'
},
link: function () {
}
}
});
Now I want to include handling of special values like undefined,NAN etc.
I already have a function that should replace value.toFixed(decimals)
but I cannot figure out how to call a function directly in the directive.
Update: the view value is being changed dynamically so it should be refreshed whenever the value changes
You need to create viewValue in the directive's scope and assign handled value to it. For typescript you might want to do something like this:
interface IValueScope extends IScope {
viewValue:number;
}
app.directive("myValue", () => {
return {
template: '{{viewValue}}',
scope: {
'value': '=myValue',
'decimals': '=myValueDecimals',
},
link: function ($scope:IValueScope) {
$scope.viewValue = $scope.value == null || Number.isNaN($scope.value)
? 0
: $scope.value.toFixed($scope.decimals);
}
}
});
const app = angular.module('app', []);
app.component('home', {
template: '<span><button ng-click="$ctrl.inc()">Inc</button> <data-val data-my-value="$ctrl.v" data-my-value-decimals="2" ></my-value> </span>',
controller: function() {
this.v = 1;
this.inc = () => this.v += 1;
}
});
app.directive("val", () => {
return {
template: '{{viewValue}}',
scope: {
'value': '<myValue',
'decimals': '<myValueDecimals',
},
link: function($scope) {
const newValue = (v) => v == null || Number.isNaN(v) ?
0 :
v.toFixed($scope.decimals)
$scope.viewValue = newValue($scope.value);
const cleanup = $scope.$watch('value', (value) => {
$scope.viewValue = newValue(value)
});
$scope.$on('$destroy', () => cleanup());
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.7.5/angular.min.js"></script>
<div ng-app="app">
<home></home>
</div>

angularjs bind to controller with isolated scope

I have a pretty simple directive and I want to use the bindToController option. So, I created my directive like this:
(function () {
'use strict';
angular.module('sapphire.directives').directive('list', list);
function list() {
return {
restrict: 'A',
template: '<div class="row flex-column" ng-class="{ \'spinner-dark\': controller.loading }" ng-include="controller.templateUrl" ng-if="controller.loading || controller.models.length"></div>',
controller: 'ListDirectiveController',
controllerAs: 'controller',
scope: true,
bindToController: {
method: '&list',
templateName: '#'
}
};
};
})();
And then I created my controller like this:
(function () {
'use strict';
angular.module('sapphire.directives').controller('ListDirectiveController', listDirectiveController);
listDirectiveController.$inject = ['ListDirectiveService', 'Selections'];
function listDirectiveController(broadcast, selections) {
var self = this;
console.log(self);
// Bindings
self.limit = 0;
self.total = 0;
self.loading = true;
self.templateUrl = 'app/directives/lists/list/' + (self.templateName || 'list-default') + '.html';
self.isSelected = selections.isSelected;
self.select = selections.select;
// Method binding
self.list = list;
init();
//////////////////////////////////////////////////
function init() {
list();
};
// Our list method
function list() {
// Set our initial limit
self.limit += 10;
self.loading = true;
// Get our items
return self.method({ limit: self.limit }).then(function (response) {
self.loading = false;
self.models = response;
self.total = response.length;
});
};
///////// ------ Removed for brevity ------ /////////
};
})();
When I use this directive I get an error stating:
self.method is not a function
which is why I am console.logging the controller to see what is bound to it. Surely enough, the method and templateName are missing.
I have tried a few ways to get this to work:
scope: {
method: '&list',
templateName: '#'
},
bindToController: true
or
scope: {},
bindToController: {
method: '&list',
templateName: '#'
}
but nothing seems to work. I can't get my isolated scope to be bound to my controller....
Does anyone know what I am doing wrong?
PS: I am using angular 1.6.4
To use the directive I do this:
<div class="invisible-container" list="controller.listUsers(limit)" template-name="users"></div>
Ok, so I figured this out. The scope is bound, but it isn't available straight away. I had to create an init method and invoke it from the directive. Only then was everything bound.
I did it like this:
(function () {
'use strict';
angular.module('sapphire.directives').directive('list', list);
function list() {
return {
restrict: 'A',
template: '<div class="row flex-column" ng-class="{ \'spinner-dark\': controller.loading }" ng-include="controller.templateUrl" ng-if="controller.loading || controller.models.length"></div>',
controller: 'ListDirectiveController',
controllerAs: 'controller',
scope: {
method: '&list',
templateName: '#'
},
bindToController: true,
link: function (scope, element, attrs, controller) {
controller.init();
}
};
};
})();
and the controller now looks like this:
(function () {
'use strict';
angular.module('sapphire.directives').controller('ListDirectiveController', listDirectiveController);
listDirectiveController.$inject = ['ListDirectiveService', 'Selections'];
function listDirectiveController(broadcast, selections) {
var self = this;
// Bindings
self.limit = 0;
self.total = 0;
self.loading = true;
self.isSelected = selections.isSelected;
self.select = selections.select;
// Method binding
self.init = init;
////////////////////////////////////////////////////
function init() {
list();
getTemplate();
bindEvents();
};
function bindEvents() {
broadcast.onPrepend(onPrepend);
broadcast.onRefresh(onRefresh);
};
function getTemplate() {
self.templateUrl = 'app/directives/lists/list/' + (self.templateName || 'list-default') + '.html';
};
function list() {
// Set our initial limit
self.limit += 10;
self.loading = true;
// Get our items
return self.method({ limit: self.limit }).then(function (response) {
self.loading = false;
self.models = response;
self.total = response.length;
});
};
function onPrepend(event, args) {
if (args && args.target && args.target === self.templateName) {
self.models.unshift(args.model);
}
};
function onRefresh(event, args) {
if (args && args.target && args.target === self.templateName) {
self.limit -= 10;
self.models = [];
list();
}
};
};
})();

Cancel function that has $timeout running

I have the following code. It checks a factory that polls a server every few minutes. It works fine, but I want to pause the "fetchDataContinously" method when a modal is opened, and resume it when the modal is closed. In short, I need to find a way to toggle the "fetchDataContinously" on the opening and closing of modals. Please advise, or let me know if my question is not clear.
angular.module('NavBar', []).controller('NavBarCtrl', function NavBarCtrl($scope,$modal,$timeout,MyPollingService) {
var ctrl = this;
fetchDataContinously ();
function fetchDataContinously () {
MyPollingService.poll().then(function(data) {
if(data.response[0].messages[0].important_popup){
ctrl.showImportantNotice(data.response[0].messages[0]);
return;
}
$timeout(fetchDataContinously, 3000);
});
}
ctrl.showNotices = function () {
var noticesModalOptions = {
templateUrl: "notices.html",
controller: "NoticesCtrl",
controllerAs: "ctrl",
show: true,
scope: $scope,
resolve: {
notices: function(NoticesFactory) {
return NoticesFactory.getMessages();
}
}
}
var myNoticesModal = $modal(noticesModalOptions);
myNoticesModal.$promise.then(myNoticesModal.show);
}
ctrl.showImportantNotice = function (importantNotice) {
ctrl.importantNotice = importantNotice;
var importantNoticeModalOptions = {
templateUrl: "/importantNotice.html",
controller: "ImportantNoticeCtrl",
controllerAs: "ctrl",
show: true,
scope: $scope,
onHide: function() {
console.log("Close !");
fetchDataContinously();
},
onShow: function() {
console.log("Open !");
}
}
var myImportantNoticeModal = $modal(importantNoticeModalOptions);
myImportantNoticeModal.$promise.then(myImportantNoticeModal.show);
}
})
Wrap your $timeout in function and return it's promise
var timer;
function startPolling(){
return $timeout(pollData, 3000);
}
// start it up
timer = startPolling();
To cancel:
$timeout.cancel(timer);
Then to start again :
timer = startPolling();

$watch in a link function on a factory/service value is not updated

Below is a directive I am using, where I am trying to update a template URL based on a variable in a factory:
.directive('poGenericNotification',['errorHandler', function(errorHandler) {
return {
controller: 'ErmModalCtrl',
restrict: 'EA',
scope: {
title: '=',
errorList: '=',
errMsg: '=',
error: '=',
info: '=',
numNotifications: '=',
messageOverflow: '='
},
template: "<div ng-include='getTemplateUrl()' class='generic-notif-container' ng-click='openErm()'></div>",
transclude: true,
link: function(scope) {
scope.$watch(errorHandler.getMostRecentError(), function(mostRecentError) {
scope.getTemplateUrl = function() {
if (mostRecentError.type === "Alert") {
return 'src/alerts/templates/error-alert.html';
}
else if (mostRecentError.type === "Info") {
return 'src/alerts/templates/info-alert.html';
}
}
}, true);
}
}
}])
Here is the factory it is referencing:
.factory('errorHandler', function () {
var errorArray = [];
var mostRecentError = {
type:'', message: '', timestamp: ''
};
function compareObjs(a,b) {
//sorting function
}
errorArray.addError = (function (type, message) {
var timestamp = Date.now();
errorArray.push({type: type, message: message, timestamp: timestamp});
errorArray.sort(compareObjs);
errorArray.generateMostRecentError();
});
//....some functions
errorArray.generateMostRecentError = function() {
if (errorArray[0].message.length > 138) {
mostRecentError.message = errorArray[0].message.slice(0, 138) + "...";
messageOverflow = true;
} else {
mostRecentError.message = errorArray[0].message;
messageOverflow = false;
}
mostRecentError.type = errorArray[0].type;
mostRecentError.timestamp = errorArray[0].timestamp;
console.log(mostRecentError);
}
errorArray.getMostRecentError = function() {
console.log(mostRecentError);
return mostRecentError;
}
return errorArray;
})
I want to be able to add/remove errors from other controllers and have it update the directive. Currently, for the initial value the $watch mostRecentError callback is undefined, then it never updates. What have I missed?
You need to pass the function to the $watch instead of its result. Change to:
scope.$watch(errorHandler.getMostRecentError, ...);
To have the function called on every digest loop you should replace
errorHandler.getMostRecentError()
by
errorHandler.getMostRecentError
otherwise you're watching the result of the function call as a variable attached to the directive's scope.

Unable to watch/observe scope?

<button-large color="green" click="createWorkstation()" busy="disableSave()" busyLabel="Saving...">Save</button-large>
I'm not able to watch changes to the output of disableSave(). The console.log()'s shown in my directive are never triggered when the output of .busy is changed. What am I doing wrong?
directive('buttonLarge', function () {
return {
scope: {
busy: '&',
click: '&'
},
replace: true,
restrict: 'E',
transclude: true,
template: '<button class="buttonL" ng-transclude/>',
link: function (scope, element, attrs) {
//when the button is busy, disable the button
if (angular.isDefined(scope.busy())) {
scope.$watch(scope.busy(), function () {
console.log('watched');
});
attrs.$observe(scope.busy(), function () {
console.log('observed');
});
}
//setup click event - https://groups.google.com/forum/#!topic/angular/-uVE5WJWwLA
if (angular.isDefined(scope.click)) {
element.bind('click', scope.click);
}
}
}
})
Controller
$scope.newWorkstationDialog = function (workflowProcess) {
var d = $dialog.
dialog({
resolve: {
workflowProcess: function () {
return workflowProcess;
}
}
}).
open('/partials/admin/'+workflowProcess.entity.slug+'/setup.htm', ['$scope', 'dialog', ..., function ($scope, dialog, ...) {
$scope.saving = false;
/* Create the workstation */
$scope.createWorkstation = function () {
console.log('saving');
$scope.saving = true;
$timeout(function () {
$scope.saving = false;
console.log('stopped saving');
}, 1000);
}
//Should the save button be disabled?
$scope.disableSave = function () {
return $scope.saving;//|| $scope.form.$valid;
}
$scope.cancel = function () {
dialog.close();
}
}]);
}
Your syntax of watching in not correct .You should be not using scope when doing watch because internally it use $parse service which internally attach scope . So you need to modify your code as below
1st option
scope.$watch(function(){
return scope.busy()
}, function (newvalue,oldvalue) {
console.log('watched');
});
2nd option
scope.$watch('busy()', function (newvalue,oldvalue) {
console.log('watched');
});

Resources