Angular $parser won't work in attribute directive with input template - javascript

I really need to leave it as a attribute, company rules.
Here is the jsfiddle:
http://jsfiddle.net/M3sZN/
I can't seem to get the console.logs to work for $parser when I type into the input box.
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
require: 'ngModel',
restrict: 'A',
scope: {
ngModel: '='
},
link: function(scope, element, attr, ngModelCntrl) {
ngModelCntrl.$formatters.unshift(function (text) {
console.log('from formatters: ');
});
ngModelCntrl.$parsers.push(function (text) {
console.log('from parsers: ');
});
},
template: '<input ng-model="ngModel" type="text">',
};
});
function Demo($scope) {
$scope.data = 'Some Value';
}
And here is the HTML:
<div ng-app="demo" ng-init="" ng-controller="Demo">
<div lowercase ng-model="data"></div>
</div>
I have tried a lot but this includes the following:
Using require: '^ngModel', instead.
Removing the scope, though I need it for the rest of the directive I'm using.
Changing up the names to use myModel to pass in the model.
And a lot of other things that I have lost track of.
Edit
I was able to get this working like this: http://jsfiddle.net/M3sZN/2/
HTML:
<div ng-app="demo" ng-init="" ng-controller="Demo">
<input lowercase ng-model="data" type="text">
</div>
JS
var module = angular.module("demo", []);
module.directive('lowercase', function() {
return {
require: 'ngModel',
restrict: 'A',
link: function(scope, element, attr, ngModelCtr) {
ngModelCtr.$formatters.unshift(function (text) {
console.log('from formatters: ');
});
ngModelCtr.$parsers.push(function (text) {
console.log('from parsers: ' + text);
return text;
});
}
};
});
module.controller('Demo', Demo);
function Demo($scope) {
$scope.data = 'Some Value';
}
By removing the template and just putting the model directly onto the input field.
This is NOT ideal, since I would like to use a template since I have a ton of attributes that need to go on the input field. I would like to avoid the duplication each time I use this directive.

Related

Angular directive not applied on change of the value

I have a directive like
testApp.directive('changeValue', function(){
alert("coming");
return {
restrict: 'E',
link: function (scope, element, attrs) {
element.text(attrs.val);
},
replace: true
};
});
and I have my views as follows
<change-value val="{{test.val}}"/>
When I refresh my page, I could see the values and I get the alerts. But when the value is changed I don't get it applied to the directive but it binds to the view as I could see it being changed outside when simply called like
<span>{{test.val}}</span>
I am not sure how this works. Thanks in Advance!
In your code, the link function is executed only once so the value is updated only when the directive instance is created. Since you want to have a watch of its value and keep updating the text you can use $observe()
var app = angular.module('my-app', [], function() {})
app.controller('AppController', function($scope) {
$scope.test = {
val: "Welcome"
};
});
app.directive('changeValue', function() {
return {
restrict: 'E',
link: function(scope, element, attrs) {
attrs.$observe('val', function() {
element.text(attrs.val);
})
},
replace: true
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="my-app" ng-controller="AppController">
<change-value val="{{test.val}}"></change-value>
<input ng-model="test.val" />
</div>

Access $scope from isolated directive

I want to change a $scope variable from inside an isolated directive, how is this possible?
I have tried using the '#, =, &' syntax in the directive scope but cannot get it to work.
This is my simplified code
JS
app.controller('testCtrl', function($scope) {
$scope.hello = 'hello';
}
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div>{{text}}</div>',
scope: {},
link: function(scope, element) {
scope.text = 'this is my text';
scope.hello = 'hello world!';
}
};
});
HTML
<body>
{{ hello }}
<test-directive />
</body>
This is the output i want
hello world!
this is my text
You can set a require option on the directive and specify a parent controller. This will pass the controller to your link function as the last argument:
app.directive('testDirective', function() {
return {
restrict: 'E',
template: '<div>{{text}}</div>',
require: '^testCtrl',
scope: {},
link: function(scope, element, attrs, testCtrl) {
scope.text = 'this is my text';
testCtrl.setHello('hello world!');
}
};
});
Note you have to create this testCtrl.setHello() method on your controller. This is because you get the controller itself, not its injected scope:
app.controller('testCtrl', function($scope) {
$scope.hello = 'hello';
this.setHello = function(newHello) {
$scope.hello = newHello;
}
}
Also, if you don't really care about strictly enforcing the controller dependency, you can directly access scope.$parent.$parent.hello from your directive.
In HTML, the directive must be in snake-case:
<test-directive />
In your script, the directive must be defined in camel case:
app.directive('testDirective', function() {
});
Also, add the ngController directive:
<body ng-controller="testCtrl>
</body>
Here's what was missing:
ng-controller was not defined
# means passing the attribute as a string, = means binding the property to a property from the parent's scope (which is what we need here) and & means passing in a function from the parents scope to be called later.
when the directive is called "testDirective" it looks in the HTML as follows:<test-directive></test-directive> as camel cases in JS need to be
seperated by "-" in HTML.
<body ng-controller="testCtrl">
{{ hello }}
<test-directive hello-from-parent="hello"></test-directive>
</body>
app.directive('testDirective', function() {
return {
restrict: 'E',
scope: {
hello: "=helloFromParent"
},
template: '<div>{{text}}</div>',
link: function(scope, element, attrs) {
scope.text = 'this is my text';
scope.hello = 'hello world';
}
}
});
I set up a working plnkr here

Dynamic NG-Controller Name

I want to dynamically specify a controller based on a config that we load. Something like this:
<div ng-controller="{{config.controllerNameString}}>
...
</div>
How do I do this in angular? I thought this would be very easy, but I can seem to find a way of doing this.
What you want to do is have another directive run before anything else is called, get the controller name from some model remove the new directive and add the ng-controller directive, then re-compile the element.
That looks like this:
global.directive('dynamicCtrl', ['$compile', '$parse',function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-ctrl'))(scope);
elem.removeAttr('dynamic-ctrl');
elem.attr('ng-controller', name);
$compile(elem)(scope);
}
};
}]);
Then you could use it in your template, like so:
<div dynamic-ctrl="'blankCtrl'">{{tyler}}</div>
with a controller like this:
global.controller('blankCtrl',['$scope',function(tyler){
tyler.tyler = 'tyler';
tyler.tyler = 'chameleon';
}]);
There's probably a way of interpolating the value ($interpolate) of the dynamic-ctrl instead of parsing it ($parse), but I couldn't get it to work for some reason.
I'm using it in ng-repeat, so this is improved code for loops and sub objects:
Template:
<div class="col-xs6 col-sm-5 col-md-4 col-lg-3" ng-repeat="box in boxes">
<div ng-include src="'/assets/js/view/box_campaign.html'" ng-dynamic-controller="box.type"></div>
</div>
Directive:
mainApp.directive('ngDynamicController', ['$compile', '$parse',function($compile, $parse) {
return {
scope: {
name: '=ngDynamicController'
},
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem, attrs) {
elem.attr('ng-controller', scope.name);
elem.removeAttr('ng-dynamic-controller');
$compile(elem)(scope);
}
};
}]);
Personally the 2 current solutions here didn't work for me, as the name of the controller would not be known when first compiling the element but later on during another digest cycle. Therefore I ended up using:
myapp.directive('dynamicController', ['$controller', function($controller) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
attrs.$observe('dynamicController', function(name) {
if (name) {
elem.data('$Controller', $controller(name, {
$scope: scope,
$element: elem,
$attrs: attrs
}));
}
});
}
};
}]);

Parser function does not get called for change in input textbox

I am new to parsers and formatters. I have a directive that will be doing validation on change of the model.One way to do this is the $watch but from what I understand that is not a good way since it allows the model to be updated.
So I was looking at parsers and tried this code
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
},
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
ctrl.$formatters.push(function(value) {
console.log("hello1");
return value;
});
ctrl.$parsers.unshift(function(value) {
debugger;
console.log("hello");
return value;
});
}
};
});
But the parser function is never called. The formatter is called once. Please see the plunkr .Can anyone tell me what I am doing wrong ,why is the parser function not getting called when i type in the textbox ?
This is a late response, but for reference:
I think you where missing the "glue" that will call the $parsers while ui changes occurs. This should be something like:
app.directive('myDirective', function($compile) {
return {
restrict: 'E',
require: 'ngModel',
scope: {
},
link: function($scope, elem, attr, ctrl) {
console.debug($scope);
ctrl.$formatters.push(function(value) {
console.log("hello1");
return value;
});
ctrl.$parsers.unshift(function(value) {
return value;
});
scope.$watch('directive model here', function() {
ctrl.$setViewValue(<build model from view here>);
});
}
};
});
For a full reference please see this (awesome) post.
Your link function is not called because the associated DOM element is not changing, just the model is. This works:
HTML:
This scope value <input ng-model="name" my-directive>
JS:
app.directive('myDirective', function($compile) {
return {
require: 'ngModel',
link: function($scope, elem, attr, ctrl) {
ctrl.$parsers.unshift(function(value) {
console.log("hello");
});
}
};
});
See here for more on when the link function is called.

ngModel, dot-rule and more than one level of scope hierarchy

Let's say I want to write HTML like:
<div my-directive my-start="topModel.start" my-end="topModel.end">
my-directive has a template that invokes other directives with ngModel, like so:
<div>
<input ng-model="myStart" />
<input ng-model="myEnd" />
</div>
I would like the inner inputs to transparently update topModel. It doesn't work this way, because there's no dot in the ng-model attribute and the value is set in local scope.
The only way I found so far is to watch both models in my-directive and translate, but it's a horrible abomination.
restrict: 'A',
scope: {
myStart: '=',
myEnd: '='
},
link: function(scope, el, attrs) {
scope.model = { start: scope.myStart, end: scope.myEnd };
scope.$watch("model.start", function(n) {
scope.myStart = n;
});
scope.$watch("model.end", function(n) {
scope.myEnd = n;
});
scope.$watch("myStart", function(n) {
scope.model.start = n;
});
scope.$watch("myEnd", function(n) {
scope.model.end = n;
});
}
How can I pass the bindings through my-directive to the inner directives without all this manual synchronization?
EDIT: See plunker at http://plnkr.co/edit/ppzVd7?p=preview - this one actually works
EDIT2: See another at http://plnkr.co/edit/Nccpqn?p=preview - this one shows how "direct access" without dot doesn't work, and with dot and $watches does.
When you define the scope property as you do on your directive, you will automatically get two properties, scope.myStart and scope.myEnd, with a bidirectional binding to topModel. When you map them over to scope.model you break that binding.
Here is a working example:
module.directive('myDirective', function () {
return {
scope: {
myStart: '=',
myEnd: '='
},
template: '<p><label>Start: <input type="text" ng-model="myStart" /></label></p>' +
'<p><label>End: <input type="text" ng-model="myEnd" /></label></p>',
link: function (scope, element, attrs) {
scope.$watch('myStart', function (val, old) {
console.log('Start changed from', old, 'to', val);
});
scope.$watch('myEnd', function (val, old) {
console.log('End changed from', old, 'to', val);
});
}
};
});
Plunker
With your 2nd example I finally understood your problem, though I still don't think it's comming from breaking the dot-rule. You're simply mixing scopes / model binding (see $parent.myStart).
Try:
(function (app, ng) {
'use strict';
app.controller('MyCtrl', ['$scope', function($scope) {
$scope.topModel = {
start: 'Initial Start',
end: 'Initial end'
};
}]);
app.directive('myDirective', function(){
return {
scope: { myStart: '=', myEnd: '=' },
template: '<div>\
<p>Start: <span special-editor-a ng-model="$parent.myStart"></span></p>\
<p>End: <span special-editor-b ng-model="myEnd"></span></p>\
</div>'
}
});
// with replace
app.directive('specialEditorA', function(){
return {
scope: true,
require: 'ngModel',
replace: true,
template: '<input type="text" />'
}
});
// without replace
app.directive('specialEditorB', function(){
return {
scope: { m: '=ngModel' },
require: 'ngModel',
template: '<input type="text" ng-model="m" />'
}
});
}(angular.module('app', []), angular));
demo: http://plnkr.co/edit/SfFBf5NgFhSOQiFf5Emc?p=preview

Categories