Angularjs $viewValue without changing $modelValue - angularjs

In Angular (1.4.x), is the there a way to dynamically change the input context without changing the $modelValue? So for example: is it possible to dynamically toggle a time (moment) input text/content for local/utc without changing the $modelValue. Here's an example that will change both view and model values. I just need to mask the input context and not the model value.
Thanks!
var app = angular.module('testparser', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {
name: ''
};
});
app.directive('changetime', function() {
return {
restrict: 'EA',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.push(function(value) {
return value;
});
//format text from the user (view to model)
ngModel.$parsers.push(function(value) {
return value;
});
scope.data.time = moment().format('HH:mm:ss')
scope.setLocalTime = function() {
scope.data.time = moment().local().format('HH:mm:ss');
}
scope.setUtcTime = function() {
scope.data.time = moment().utc().format('HH:mm:ss');
}
}
}
});
<html ng-app="testparser">
<head>
<meta charset="utf-8" />
<script data-require="angular.js#1.4.8" data-semver="1.4.8" src="https://code.angularjs.org/1.4.8/angular.js"></script>
<script data-require="moment.js#*" data-semver="2.10.2" src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.10.2/moment.min.js"></script>
</head>
<body ng-controller="MainCtrl">
<input type="button" value="set to local" ng-click="setLocalTime()" />
<input type="button" value="set to utc" ng-click="setUtcTime()" />
<input changetime ng-model="data.time" />
<pre>model is: {{data.time}}</pre>
</body>
</html>

You were really close.
var app = angular.module('testparser', []);
app.controller('MainCtrl', function($scope) {
$scope.data = {
name: ''
};
});
app.directive('changetime', function() {
return {
restrict: 'EA',
require: 'ngModel',
link: function(scope, element, attrs, ngModel) {
//format text going to user (model to view)
ngModel.$formatters.unshift(function(value) {
return value;
});
//format text from the user (view to model)
ngModel.$parsers.unshift(function(value) {
return value;
});
scope.data.time = moment().format('HH:mm:ss')
scope.setLocalTime = function() {
ngModel.$viewValue = moment().local().format('HH:mm:ss');
ngModel.$render();
//scope.data.time = moment().local().format('HH:mm:ss');
}
scope.setUtcTime = function() {
ngModel.$viewValue = moment().utc().format('HH:mm:ss');
ngModel.$render();
//scope.data.time = moment().utc().format('HH:mm:ss');
}
}
}
});

Related

How to expose directive methods using a service

How to expose directive methods without using $broadcast or '=' between modules?
Using $broadcast (events) if there are multiple directives all will be notified. It cannot return value too.
Exposing directive's function by html attribute I think it is not that best that Angular has to offer.
Angular Bootstrap UI do it using services (I guess): It have a service named "$uibModal".
You can call a function "$uibModal.open()" of Modal Directive by injecting $uibModal service.
Is that the right way?
An example of a directive that registers its API with a service:
app.service("apiService", function() {
var apiHash = {};
this.addApi = function (name,api) {
apiHash[name] = api;
};
this.removeApi = function (name) {
delete apiHash[name];
};
this.getApi = function (name) {
return apiHash[name];
};
});
app.directive("myDirective", function (apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs)
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.removeApi(name);
});
}
});
Elsewhere in the app, the title of the directive can be set with:
apiService.getApi('myDirective').setTitle("New Title");
Notice that the directive registers the api with a name determined by the name attribute of the directive. To avoid memory leaks, it unregisters itself when the scope is destroyed.
Update
How could I use it from a controller?
app.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<body ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
The DEMO
angular.module('myApp', [])
.service("apiService", function() {
var apiHash = {};
this.addApi = function(name, api) {
apiHash[name] = api;
};
this.getApi = function(name) {
return apiHash[name];
};
})
.directive("myDirective", function(apiService) {
return {
restrict: 'E',
scope: {},
template: `<h1>{{title}}</h1>`,
link: postLink
};
function postLink(scope, elem, attrs) {
var name = attrs.name || 'myDirective';
var api = {};
api.setTitle = function(value) {
scope.title = value;
};
apiService.addApi(name, api);
scope.$on("$destroy", function() {
apiService.addApi(name, null);
});
}
})
.controller('home', function($scope,apiService) {
$scope.title = "New Title";
$scope.setTitle = function() {
apiService.getApi('mainTitle').setTitle($scope.title);
};
})
<script src="//unpkg.com/angular/angular.js"></script>
<body ng-app="myApp" ng-controller="home">
<my-directive name="mainTitle"></my-directive>
<p>
<input ng-model="title" />
<button ng-click="setTitle()">Set Title
</button>
</p>
</body>
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
this service exposes function charCount();
in your directive you have to inject it like this
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
and, of course call it
$scope.strLen = myService.charCount( $scope.myTestString );
<html>
<style>
#out {
width:96%;
height:25%;
padding:10px;
border:3px dashed blue;
font-family: monospace;
font-size: 15px;
}
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.6/angular.min.js"></script>
<script>
var APP = angular.module('MYAPP', []);
APP.controller('main', ['$scope', '$element', '$compile', 'myService', function($scope, $element, $compile, myService) {
$scope.test = 'my Test Controller';
$scope.directiveTest = "directive test";
var testSvc = myService.charCount($scope.test);
$scope.showTestDir = true;
}])
.directive('testDirective', ['myService', function(myService) {
return {
restrict: 'A',
replace: true,
template: "<div>'{{myTestString}}' has length {{strLen}}</div>",
link: function($scope, el, attrs) {
$scope.myTestString = 'string of length 19';
$scope.strLen = myService.charCount( $scope.myTestString );
}
}
}])
.factory('myService', [function() {
return {
charCount: function(inputString) {
return inputString.length;
}
}
}])
.filter('toUpper', function() {
return function(input) {
return input.toUpperCase();
}
})
.filter('toLower', function() {
return function(input) {
return input.toLowerCase();
}
})
;
</script>
<body ng-app="MYAPP">
<div id="out" ng-controller="main">
{{test}} - not filtered
<br/>
{{test|toUpper}} - filtered toUpper
<br/>
{{test|toLower}} - filtered toLower
<br/>
<br/>
<div test-directive ng-if="showTestDir"></div>
</div>
</body>
</html>

Why auto focus directive does not work

Auto focus directive is not working look at my code
i think there is some problem
var app = angular.module('angularjs-starter', []);
app.controller('MainCtrl', function($scope) {});
app.directive('autoFocus', ['$timeout', function ($timeout) {
return {
restrict: 'AC',
link: function (scope, element, attrs, ctrl) {
setTimeout(function () {
if ($(element)) {// if value is already filled
if (attrs.focusNext) { // if next Field provided then put focus on next field
var target = angular.element('#' + attrs.focusNext);
target.focus();
}
else element.focus();
}
else element.focus(); //setting current value focus
}, 600);
}
};
}])
I think you need to put index in element like this
app.directive('autoFocus', ['$timeout', function ($timeout) {
return {
restrict: 'AC',
link: function (scope, element, attrs, ctrl) {
setTimeout(function () {
if ($(element[0]).val()) {// if value is already filled
if (attrs.focusNext) { // if next Field provided then put focus on next field
var target = angular.element('#' + attrs.focusNext);
target[0].focus();
}
else element[0].focus();
}
else element[0].focus(); //setting current value focus
}, 600);
}
};
}])
hope it will help you
As #hope said you have to use index to access to DOM elements but you also need to use element[0].value to access to the element value.
And finally you need to use angular.element(document.getElementById(attrs.focusNext)) to find an element by id, like this :
var app = angular.module('plunker', []);
app.directive('autoFocus', ['$timeout', function($timeout) {
return {
restrict: 'AC',
link: function(scope, element, attrs, ctrl) {
setTimeout(function() {
if (element[0].value) { // if value is already filled
if (attrs.focusNext) { // if next Field provided then put focus on next field
var target = angular.element(document.getElementById(attrs.focusNext));
target[0].focus();
} else element[0].focus();
} else element[0].focus(); //setting current value focus
}, 600);
}
};
}])
app.controller('MainCtrl', function($scope) {
$scope.test = "first input already filled"
});
<script data-require="angular.js#1.5.x" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.11/angular.min.js" data-semver="1.5.11"></script>
<body ng-app="plunker" ng-controller="MainCtrl">
<input ng-model="test" auto-focus focus-next="secondField">
<input id="secondField">
</body>

Angular directive compile: "RangeError: Maximum call stack size exceeded"

I want to add an 'ng-pattern' directive to an input element through a custom directive. I don't want to do it in the templates directly, but it looks I'm getting in a infinite loop.
I tried to set first the 'html' and compile the element after (Angular compile in directive seems to go into infinite loop) but scope is undefined. I don't know if it's related with replacing element's content.
Should i create a new scope? Do I'm missing something?
Thanks in advance!
var myHtml = iElem[0].outerHTML;
iElem.replaceWith(myHtml);
var compiledElement = $compile(iElem)(iElem.scope());
HTML:
<input type="text" ng-model="personal.testNumber_string" my-model="personal.testNumber" dot-to-comma>
Directive:
function dotToCommaConverter($compile) {
return {
require: 'ngModel',
restrict: 'A',
scope: {
myModel: '='
},
controllerAs: 'dot2Comma',
controller: function($scope) {
this.myModel = $scope.myModel;
},
compile: function(tElem, tAttrs) {
return {
pre: function(scope, iElem, iAttrs) {
},
post: function(scope, iElem, iAttrs, modelCtrl) {
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
var compiledElement = $compile(iElem)(iElem.scope());
iElem.replaceWith(compiledElement);
modelCtrl.$setViewValue(String(scope.dot2Comma.myModel).replace('.', ','));
modelCtrl.$render();
modelCtrl.$parsers.push(function(inputValue) {
var transformedInput = inputValue.replace(/[^0-9,.-]/g, '');
transformedInput = transformedInput.replace('.', ',');
transformedInput = transformedInput.replace(' ', '');
if (transformedInput !== inputValue) {
modelCtrl.$setViewValue(transformedInput);
modelCtrl.$render();
}
if (!isNaN(Number(transformedInput.replace(',', '.')))) {
scope.myModel = Number(transformedInput.replace(',', '.'));
} else {
scope.myModel = undefined;
}
return transformedInput;
});
}
};
}
};
}
I needed to remove my own directive from the Html content before re-compiling again, that's what caused the infinite loop.
iElem.removeAttr('dot-to-comma');
iElem.attr('ng-pattern', '/^-?[0-9]+(?:\,[0-9]+)?$/');
iElem.attr('ng-blur', 'dot2Comma.myBlurFunction()');
var compiledElement = $compile(iElem)(scope);
iElem.replaceWith(compiledElement);
here is an sample directive which replace dots with commas in a textbox :
script.js
angular.module('app', []);
angular.module('app')
.controller('ExampleController', ['$scope', function($scope) {
$scope.my = { number: '123.456' };
}]);
angular.module('app')
.directive('dotToComma', function() {
return {
restrict: 'A',
link: function (scope, element, attrs) {
scope.$watch(attrs.ngModel, function (value) {
var newValue = value.replace('.', ',');
element.val(newValue);
});
}
}
});
index.html
<html lang="en" ng-app="app">
<head>
<meta charset="utf-8">
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.6.1/angular.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<form ng-controller="ExampleController">
<p>scope.my.number = {{my.number}}</p>
<label>In this textbox, dots will automatically be replaced with commas, even if you change its value :</label>
<input type="text" ng-model="my.number" dot-to-comma>
</form>
</body>
</html>
Here is a plunker : https://plnkr.co/edit/X6Fi0tnjBXKKhbwH0o2q?p=preview
Hope it helps !

Sharing logic between directives

I'm trying to share the logic between these two directives, I'm still learning Angular and don't quite understand how to accomplish this. I'm getting a $compile:ctreq error. I have watched some tutorials and I believe the logic is supposed to be in the controller but I get an error and the page wont load. I have a simple Pomodoro timer and would like the buttons to each be there own directive. Maybe I should be doing this with controllers but either way I would like to know how this works. Thanks..
var app = angular.module('pomodoro_timer', ['ui.router', 'firebase']);
app.directive("timer", ['$interval', function($interval) {
return {
restrict: "E",
transclude: true,
controller: function() {
},
templateUrl: "/templates/timer.html",
link: function(scope,element,attributes) {
scope.intrvl;
scope.t = 10;
var tDiv = $(element).find('#time');
scope.min = "25";
scope.sec = "00";
scope.interval = function() {
scope.intrvl = $interval(function(){
if (scope.t == 0) {
scope.resetTimer();
scope.sessionComplete = false;
} else {
scope.t -= 1;
scope.displayTime()
}
},1000)
}
scope.toggleClass = function() {
tDiv.toggleClass('notWorking working');
}
}
};
}]);
app.directive('start', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
require: "^timer",
templateUrl: '/templates/start.html',
link: function (scope, element, attr, timerCtrl) {
scope.startTimer = function() {
if (tDiv.hasClass("notWorking")) {
// scope.working = true;
scope.interval(scope.t);
scope.toggleClass();
}
};
}
};
});
HTML
<!DOCTYPE html>
<html ng-app="pomodoro_timer">
<head lang="en">
<title>Pomodoro Timer</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="stylesheet" href="/css/style.css">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.2/angular.min.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/angular-ui-router/0.2.8/angular-ui-router.min.js"></script>
<script src="https://cdn.firebase.com/js/client/2.2.4/firebase.js"></script>
<script src="https://cdn.firebase.com/libs/angularfire/1.1.1/angularfire.min.js"></script>
</head>
<body>
<timer></timer>
<start></start>
<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
<script type="application/javascript" src="/js/app.js"></script>
</body>
</html>
As you are using require: '^timer' inside start, that means you are assuming that your start directive should be inside timer directive so that start directive can get access to the controller of timer directive.
Also you should place the expo-sable method inside controller rather than placing it into link function, controller could be accessible by the directive which require this controller.
Markup
<timer>
<start></start>
</timer>
Code
app.directive("timer", ['$interval', function($interval) {
return {
restrict: "E",
transclude: true,
controller: function($scope) {
$scope.interval = function() {
$scope.intrvl = $interval(function() {
if (scope.t == 0) {
$scope.resetTimer();
$scope.sessionComplete = false;
} else {
$scope.t -= 1;
$scope.displayTime()
}
}, 1000)
};
$scope.toggleClass = function() {
tDiv.toggleClass('notWorking working');
};
},
templateUrl: "/templates/timer.html",
link: function(scope, element, attributes) {
scope.intrvl = 0; //set default value
scope.t = 10;
var tDiv = $(element).find('#time');
scope.min = "25";
scope.sec = "00";
}
};
}]);
app.directive('start', function() {
return {
restrict: 'E',
transclude: true,
scope: {},
require: "^timer",
templateUrl: '/templates/start.html',
link: function(scope, element, attr, timerCtrl) {
scope.startTimer = function() {
if (tDiv.hasClass("notWorking")) {
//calling method of `timer` directive controller
timerCtrl.interval(scope.t);
timerCtrl.toggleClass();
}
};
}
};
});

Data attribute change not being observed

I'm changing data attribute of a dom element and using $observe within the directive to detect for any changes but it doesn't seem to work after clicking on the "change data" button
HTML
<body ng-controller="MainCtrl">
<div id="container" data-name="somename" mydirective>Data</div>
<button ng-click="changeData();">Change Data Attribute</button>
</body>
JS
app.controller('MainCtrl', function($scope) {
$scope.changeData = function() {
var el = document.querySelector('#container');
angular.element(el).attr('data-name', 'hello');
}
});
app.directive('mydirective', function() {
return {
link : function(scope, element, attrs, ngModel) {
attrs.$observe("name", function (newValue) {
console.log(newValue);
});
}
}
Plnkr : http://plnkr.co/edit/saM7fO0DdsaaDBW7ADQH?p=preview
why dont you use expression data-name="{{datax}}" ?
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope) {
$scope.datax = "somename";
$scope.changeData = function() {
//var el = document.querySelector('#container');
//angular.element(el).attr('data-name', 'hello');
$scope.datax = "hello";
}
});
app.directive('mydirective', function() {
return {
link : function(scope, element, attrs, ngModel) {
attrs.$observe("name", function (newValue) {
console.log(newValue);
});
}
}
});

Resources