Angular JS $interpolate query - javascript

I newbie to Angular JS and way trying to understand directive functionality which is written as below
function mydirective($interpolate, $compile) {
return {
restrict: 'E',
scope: {
mycontent: '=',
myurls: '=',
mydata: '#'
},
replace: true,
template: '<div ng-bind-html="html"></div>',
link: function ($scope, element, attrs) {
$scope.$watch('mycontent', function (value) {
var html = $interpolate(value)($scope);
element.html(html);
$compile(element.contents())($scope);
});
}
}
}
I am not able to understand following from above.
1) What does this $interpolate(value)($scope) do ? what is this second argument $scope.
2) What is this $compile function doing ?
3) div ng-bind-html="html" in template what does it do ?

What does this $interpolate(value)($scope) do?
Gets the literal value of the value variable
what is this second argument $scope.
It provides the context for data-binding
it('should interpolate with undefined context', inject(
function($interpolate)
{
expect($interpolate("Hello, world!{{bloop}}")()).toBe("Hello, world!")
}
));
What is this $compile function doing?
It converts the string into Angular markup
div ng-bind-html="html" in template what does it do?
It compiles the HTML string within $scope.html and adds it to the markup
References
AngularJS Guide: Scope Watch Depths
AngularJS Source: interpolateSpec.js
AngularJS Expression Security Internals
Difference in $interpolate between AngularJS 1.0 and 1.2

Related

how can I pass 'scope' argument outside the directive

how can I pass 'scope' argument outside the directive?
i need use it in some other component..
my code:
(function () {
angular.module('dmv.shared.components').
directive('doImportPackage', ['Package', function (Package) {
return {
restrict: 'A',
scope: {
onStart: '<',
onFinish: '<',
onError: '<'},
link: function (scope, element, attributes) {
}
tnx !!
You can do this via a controller. Since AngularJS works in 2-way data binding principle, these variables you assigned will already be updated from where you referenced, and you can use them with other directives too. For example, I assume that you use your directive as follows:
<do-import-package
on-start="myCtrl.onStart"
on-finish="myCtrl.onFinish"
on-error="myCtrl.onError">
</do-import-package>
You have following corresponding variables in myCtrl controllor:
this.onStart = some value;
this.onFinish = some value;
this.onErrod = some value;
Under normal conditions, you can bind other directive's attributes to these values and they will be updated in 2-way. For example, if you use the following directive, both directives should be updated with the same values.
<other-directive
on-start="myCtrl.onStart"
on-finish="myCtrl.onFinish"
on-error="myCtrl.onError">
</other-directive>

What is the point of controller.$viewValue/controller.$modelValue?

I'm unclear what the relation is between scope.ngModel and controller.$viewValue/controller.$modelValue/controller.$setViewValue() is, and specifically, what the point of the latter three is. For example, see this jsfiddle:
<input type="text" ng-model="foo" my-directive>
and:
myApp.directive('myDirective', function($timeout) {
return {
require: 'ngModel',
restrict: 'A',
scope: { ngModel: '=' },
link: function (scope, element, attrs, controller) {
function log() {
console.log(scope.ngModel);
console.log(controller.$viewValue);
console.log(controller.$modelValue);
}
log();
controller.$setViewValue("boorb");
log();
scope.$watch('ngModel', function (val) {
console.log("val is now", val);
});
$timeout(function () {
log();
}, 2000);
}
}
});
With the controller being:
function MyCtrl($scope, $timeout) {
$scope.foo = 'ahha';
$timeout(function () {
$scope.foo = "good";
}, 1000);
}
The output is:
(index):45 ahha
(index):46 NaN
(index):47 NaN
(index):45 ahha
(index):46 boorb
(index):47 boorb
(index):53 val is now ahha
(index):53 val is now good
(index):45 good
(index):46 boorb
(index):47 boorb
controller.$viewValue did not start out as the value of the foo variable. Further, controller.$setViewValue("boorb") didn't influence scope.ngModel at all, nor was the update reflected in the HTML. Thus it seems there is no relation between scope.ngModel and controller.$viewValue. It seems that with anything I'd want to do, I would just use scope.ngModel, and watch those values. What is ever the point of using controller.$viewValue and controller.$modelValue or keeping them up to date with scope.ngModel?
scope: { ngModel: '=' }, creates an isolated scope for the directive, which means that changes to foo in the directive will no longer be reflected in the parent scope of MyCtrl.
Also, changes made by $setViewValue() will not get reflected in the DOM until controller.$render() is called, which tells Angular to update the DOM in the next digest cycle.
But to answer the question, NgModelController and its methods are really only necessary if you need to create some extra-special-custom-fancy data-binding directives. For normal data input and validation, you shouldn't ever need to use it. From the documentation (emphasis mine):
[NgModelController] contains services for data-binding, validation, CSS updates, and value formatting and parsing. It purposefully does not contain any logic which deals with DOM rendering or listening to DOM events. Such DOM related logic should be provided by other directives which make use of NgModelController for data-binding to control elements. Angular provides this DOM logic for most input elements.
The confusion here is coming from sticking a directive onto an existing directive, namely ngInput.
Instead, consider a fresh directive:
<my-directive ng-model="ugh">Sup</my-directive>
With:
$rootScope.ugh = 40;
And:
.directive('myDirective', function () {
return {
require: "ngModel",
// element-only directive
restrict: "E",
// template turns the directive into one input tag
// 'inner' is on the scope of the *directive*
template: "<input type='text' ng-model='inner'/>",
// the directive will have its own isolated scope
scope: { },
link: function (scope, element, attrs, ngModelCtrl) {
// formatter goes from modelValue (i.e. $rootScope.ugh) to
// view value (in this case, the string of twice the model
// value + '-'
ngModelCtrl.$formatters.push(function (modelValue) {
return ('' + (modelValue * 2)) + '-';
});
// render does what is necessary to display the view value
// in this case, sets the scope.inner so that the inner
// <input> can render it
ngModelCtrl.$render = function () {
scope.inner = ngModelCtrl.$viewValue;
};
// changes on the inner should trigger changes in the view value
scope.$watch('inner', function (newValue) {
ngModelCtrl.$setViewValue(newValue);
});
// when the view value changes, it gets parsed back into a model
// value via the parsers, which then sets the $modelValue, which
// then sets the underlying model ($rootScope.ugh)
ngModelCtrl.$parsers.push(function (viewValue) {
var sub = viewValue.substr(0, viewValue.length-1);
return parseInt(sub)/2;
});
}
};
})
Try it on Plunker.
Note that typeof ugh stays "number", even though the directive's view value is of a different type.

AngularJS - dynamically load templateURL when passing object into attribute

In my controller HTML I am passing an object into a directive as such:
<div cr-count-summary countdata="vm.currentCountData"></div>
The vm.currentCountData is an object that is returned from a factory
My directive code is below:
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
templateUrl: function(element, attrs) {
if (attrs.countdata.type === 'Deposit') {
return 'app/count/countsummary/countDeposit.html';
} else {
return 'app/count/countsummary/countRegisterSafe.html';
}
}
}
}
I have verified that vm.currentCountData is a valid object with a .type property on it. However, it doesn't recognize it. I have tried simplifying things by just passing in countdata="Deposit" in the controller HTML. I've also changed attrs.countdata.type to just attrs.countdata and it does evaluate as the string.
When I have it set up as I have shown above the directive templateUrl function seems to evaluate prior to the controller
I've looked at this, but it seems to only be evaluating strings
What do I need to do in order to allow attrs recognize the object?
This is not possible in this way, because at the time of evaluating templateUrl function angular doesn't have any scope variable, scope gets created after the compile function of directive generates preLink & postLink.
I'd prefer you to use ng-include directive inside the directive template, and then on basis of condition do load the desired template in it.
Markup
<div cr-count-summary count-data="vm.currentCountData"></div>
Directive
function countSummary() {
var directive = {
scope: {
countData: '='
},
link: link,
template: "<div ng-include=\"countdata.type === 'Deposit' ? "+
"'app/count/countsummary/countDeposit.html' :" +
"'app/count/countsummary/countRegisterSafe.html'\">"+
"</div>"
}
}

AngularJS: how to access the topmost scope without navigating via multiple $parent?

I know there is a lot of question on this, but i couldn't find my answer in them.
I have a directive for my popups that have many templates,
HTML
<popup template="popupTemplate"></popup>
Directive
app.directive('popup', function () {
return {
restrict: 'E',
scope: {
template: '='
},
link: function ($scope, $element, $attrs) {
// do something on $scope.template
}
}
});
now on another element I define the name of template for the target popup
<button popup-template="upload-avatar"></button>
directive
app.directive('popupTemplate', function () {
return {
link: function ($scope, $element, $attrs) {
$element.bind('click', function () {
$scope.$parent.popupTemplate = $attrs.popupTemplate;
$scope.$apply();
});
}
}
});
the problem:
when I'm clicking on an element inside a nested directive. cause I need to deal with:
$scope.$parent.popupTemplate
$scope.$parent.$parent.popupTemplate
It's not a good idea. I need to know how to access to first parent scope with a unique syntax instead of multiple $parent.
Just wrap popupTemplate:
<popup template="whatever.popupTemplate"></popup>
Then u can:
$scope.whatever.popupTemplate = $attrs.popupTemplate;
Without $parent at all.
Lets say u have parent scope A and child B. By default B copies all values from A. Copies here means coping pointer.
Compare in java:
void bad(String s) {
s = "new";
}
void good(String[] s) {
s[0] = "new";
}

Manually applying the ngModel directive

My directive needs to use ngModel.
I need to do this dynamically from within another directive as I want to do some funky stuff with scopes and abstract this away from the person writing the HTML.
My first thought was to use the $set function provided by the attrs argument in the link function, that works to modify the HTML but the directive itself does not get compiled. We can then combine this with the $compile provider, and it works.
attrs.$set('ngModel', someVar);
$compile(element)(scope);
The problem is that this creates infinite recursion if I do not (and I can not) replace the elements tag as the directive gets reapplied and recompiled indefinitely.
However I can fiddle with the priorities and get that to work:
module.directive('input', [
'$compile',
function($compile) {
return {
restrict: 'E',
scope: {},
priority: 100, // Set this high enough to perform other directives
terminal: true, // Make sure this is the last directive parsed
link: function(scope, element, attrs) {
var key = 'example';
attrs.$set('ngModel', key);
$compile(element, null, 100)(scope);
}
};
}
]);
This works fine, but it just feels wrong:
I now have to ensure that all other directives on the element are
capable of being recompiled as they will all get compiled twice.
I have to make sure that nobody uses a higher priority.
So this got me thinking why can't I just inject the ngModelDirective and force compile it against my element?
module.directive('input', [
'ngModelDirective',
function(ngModel) {
return {
restrict: 'E',
scope: {},
priority: 100, // Set this high enough to perform other directives
terminal: true, // Make sure this is the last directive parsed
require: '?^form',
link: function(scope, element, attrs, formCtrl) {
var key = 'example';
attrs.$set('ngModel', key);
var ngModelFactory = ngModel[0];
var ngModelLink = ngModelFactory.compile(element);
ngModelLink.call(this, scope, element, attrs, [ngModelFactory.controller, formCtrl]);
}
};
}
]);
See: https://github.com/angular/angular.js/blob/v1.2.x/src/ng/directive/input.js#L1356
No errors thrown, but nothing happens. It seems this isn't enough to hook it up, so my question is can anyone elaborate on to what I need to do link the ngModelDirective to my custom directive without forcing a recompile?
ngModel seems a bad fit for what you are trying to do. But you don't need it anyway. You can two-way-bind some variable and pass the name into the model directive scope:
app.directive("myDirective", function() {
// ...
scope: {
myModel = "=",
modelName = "myModel"
// ...
}
// ...
});
app.directive("ngModelDirective", function() {
// ...
// ...
transclude: true,
link: function(scope, element, attrs) {
var modelName = scope.modelName;
console.assert(modelName, '`modelName` must be set when using `ngModelDirective`.');
// TODO: Check if `scope[modelName]` is actually bound
doSomethingFancyWith(scope, modelName);
}
});
Template example:
<myDirective ngModelDirective my-model="..." />
Note that doSomethingFancyWith can read and write the model variable, with bindings to the outside world.
I don't think it is possible without a re-compile.
The ngModel is designed to be a kind of collaborator between other directives in the same element and also parent form diretives. For example, during complilation:
other directives (e.g. input, required or ng-change) may add its own $parser or $formatter to ngModel.
ngModel will add itself to a parent form directive if exists.
Therefore, if the ngModel is somehow added after the complication process is ended already, the above two actions will be missing.
Edit: In case the value to be assigned to ng-model attribute is known at the compile time, it is possible and will be something like this:
app.directive('myNgModel', function($compile) {
return {
restrict: 'A',
replace: false,
priority: 1000,
terminal: true, // these terminal and priority will stop all other directive from being compiled.
link: function (scope, element, attrs) {
var key = 'example';
attrs.$set('ngModel', key);
attrs.$set('myNgModel', null); // remove itself to avoid a recusion
$compile(element)(scope); // start compiling other directives
}
};
});
Here is the plunker with example: http://plnkr.co/edit/S2ZkiVIyq2bOK04vAnFO?p=preview
I've managed to do it. It's not the prettiest thing but it works and I can hook up my input directive to work using the native inputDirective so that it can use things like require or validate specific input types.
To build this against another standard directive that implements specific ngModel functionality such as ngChange just replace the injected inputDirective with the correct directive e.g., ngChangeDirective.
module.directive('input', function() {
return {
restrict: 'E',
scope: {},
require: '?ngModel',
priority: -1,
link: function(scope, element, attrs, ngModel) {
var key = 'example.property';
if (ngModel === undefined) {
attrs.$set('ngModel', key);
angular.injector(['ng']).invoke([
'inputDirective',
'ngModelDirective',
'$controller',
'$exceptionHandler',
'$parse',
'$animate',
function(inputDirective, ngModelDirective, $controller, $exceptionHandler, $parse, $animate) {
var ngModelFactory = ngModelDirective[0];
var ngModelLink = ngModelFactory.compile(scope); // Get the ngModel linkage function against this scope
ngModel = $controller(ngModelFactory.controller, {
$scope: scope,
$exceptionHandler: $exceptionHandler,
$attrs: attrs,
$element: element,
$parse: $parse,
$animate: $animate
}); // Call the ngModel controller and bootstrap it's arguments
// Call the inputDirective linkage function to set up the ngModel against this input
inputDirective[0].link(scope, element, attrs, ngModel);
element.data('$ngModelController', ngModel); // Allow additional directives to require ngModel on this element.
}
]);
}
}
};
});
NOTE: This will not work for ngOptions as it specifies terminal: true.

Categories

Resources