Isolation of directives with AngularJS - javascript

I'm new to Angular. I created a small directive where the user can input a timestamp and add/substract 1 day. The directive needs to be isolated so that it can appear multiple times in my application.
This is the part where I initialize my dates:
$scope.dateFrom = {
label : 'Date From',
date : mii.utils.date.dateToIntDate(start)
}
$scope.dateUntil = {
label : 'Date Until',
date : mii.utils.date.dateToIntDate(end)
}
In the HTML of my view I create 2 instances of my directive:
<date-input date="dateFrom"></date-input>
<date-input date="dateUntil"></date-input>
The result looks like the below image. Two seperate input fields, each has its own label and default value. So far the isolation seems to be working.
The problem is however, when I click the plus sign of Date From, it will add a day to the value of Date Until. When I look in the debugger on function _addDays I see that $scope.dateInfo is indeed pointing to the dateUntil object even though I am interacting with date From. What am I missing?
date.html
<div id="date-input">
<span>
<img src="assets/img/minus.png" class="icon left" ng-click="yesterday()"/>
{{dateInfo.label}}
<img src="assets/img/add.png" class="icon right" ng-click="tomorrow()"/>
</span>
<input ng-model="dateInfo.date" class="center"/>
</div>
dateController.js
app.directive("dateInput", function() {
return {
restrict: "E",
templateUrl : "app/shared/dateInput/date.html",
scope : {
dateInfo : "=date"
},
link : function($scope, $element, $attrs) {
$scope.yesterday = function(){
_addDays(-1);
};
$scope.tomorrow = function(){
_addDays(1);
}
_addDays = function(days){
var d = mii.utils.date.intDateToDate($scope.dateInfo.date);
var newD = new Date(d);
newD.setDate(d.getDate()+days);
$scope.dateInfo.date = mii.utils.date.dateToIntDate(newD);
}
}
}
});

To fix this, all you need to do is prepend var to the function definition:
var _addDays = function(days){
It was a simple oversight -- Because it didn't specifically state its scope, _addDays is interpreted as a global variable. This means that the second time the function is declared, it clobbers the definition of the first. Even though the function body looks the same, the closure is different in each context.

You're two way binding the dateInfo property to the parent scope (in your case, the parent scope is probably the controller scope). You need to isolate this property to its own child scope after it inherits its initial value from $scope.date from the controller. You can do this with one way data binding
scope : {
dateInfo : "#date"
},
Notice the = has been changed to #
This allows dateInfo to inherit its initial value from $scope.date in your controller. But once that value is inherited, any changes made to the child scope within the directive itself will not bubble up to $scope.date in the controller scope. You'll then have two child scopes, each with a separate dateInfo property that does not corrupt the other.

Related

How to assign value to ng-model if it is empty

I want to assign value to ng-model if it is empty. I have assign default value to ng-model input from controller but when user makes change to remove that value and makes input empty then I want to assign default value to ng-model.
For example.
Controller:
App.controller('mainController',['$scope', function($scope) {
$scope.assignOne= 16;
}]);
View:
<input ng-model="assignOne" ng-change="changeMe()"/>
Now, input value becomes 16.
If user make changes and manually removes this value 16 then I want default value 1 instead of empty string in changeMe() function.
Note:
I can check for empty ng-model and override value of ng-model in controller. But, Is there any way better way to use, something which angular provides.
I am just new to AngularJS.
Try $watch as follows
$scope.$watch(
"assignOne",
function handleChange( newValue, oldValue ) {
console.log( "assignOne:", newValue );
if(newValue == '') {
newValue = 16;
}
}
);
another solution to meet your sample:
App.controller('mainController',['$scope', function($scope) {
$scope.assignOne= 16;
// function called by " ... ng-change="changeMe() "
$scope.changeMe = function() {
if ($scope.assignOne == '') {
$scope.assignOne = 1;
}
}
}]);
PS: for the previous answer, there is a little miss: it doesn't change the "assignOne" var, but only a local copy, which is lost at the end of the function call... Correct it by changing $scope.assignOne !
PS 2: the 2 solutions are good, but can resolve different requirements :
$watch is like a trigger on the model object, and is the most efficient if you want check business rules on the navigator side
ng-change is more interesting for handling some interactions, which are not stricts rules...
The 2 ways could be used...

angularjs directive to manipulate text values

I want to create angular directives to change or format binded text values.
My values are like this: var priceList = [12.90, 15.90, 80, 55.90];
I want to use a directive and write priceList values as currency format.
<li ng-repeat"price in priceList">
<span currency>{{price}}</span>
</li>
and directive
angular
.module("app.financal")
.directive("currency", [function () {
return {
restrict: "A",
link: function (scope, element, attribute) {
// if currency vale is not null.
var curr = element.html() + "$";
element.html(curr);
}
}
}]);
how can I get <span currency>{{price}}</span> element price value and change in directive.
More simple than a directive, you can use the currency filter to format your data with currency. It already exists in Angular. Filters are used to format displayed data in Angular.
<li ng-repeat"price in priceList">
<span>{{price | currency}}</span>
</li>
See the docs for more details (you can add a symbol if you want).
It may be that you're looking to write your own custom filters, so here's some literature on how to do that:
https://docs.angularjs.org/guide/filter
Consider the following code:
.filter('customCurrency', function() {
return function ( input ) {
// if currency value is not null.
var out = input + '$';
return out;
};
})
This will do what you have outlined above if you change your html to read:
<li ng-repeat="price in priceList">
<span>{{price | customCurrency}}</span>
</li>
#e666's answer will get you to the desired end result. If you're looking to do the work inside the directive you're going to have to access the bound value of the variable directly.
Before we crack into that, I just want to point out that there are two barriers inside the code as written that we should address before moving on. The first is that var priceList = [ 12.90, 15.90, 80, 55.90 ]; isn't currently on $scope. We can fix this by defining it as $scope.priceList = [ 12.90, 15.90, 80, 55.90 ];.
Next, you'll need to ensure that your ng-repeat is assigned a value. ng-repeat"price in priceList" should therefore be rewritten as an assignment of ng-repeat="price in priceList". You'll then have access to scope.price inside the directive. Sorry for the fussy little details, but they needed to be addressed in order to get price into your directive's scope.
As for the directive, as it sits currently, element.html() will return a value of {{price}}, so that's not what we want. Since scope.price is bound data, we can now modify it directly inside the directive to achieve the desired result.
So your HTML will be slightly modified as outlined above:
<li ng-repeat="price in priceList">
<span currency>{{price}}</span>
</li>
and your directive will be:
angular
.module("app.financal")
.directive("currency", [function () {
return {
restrict: "A",
link: function (scope, element, attribute) {
// if currency vale is not null.
scope.price = scope.price + "$";
}
}
}]);
Please keep in mind that this is going to return a list with the "$" appended to the end of the string, so the output will be:
12.9$
15.9$
80$
55.9$
Lastly, here's a little (tangentially) related reading for you:
Using Filters With Directives in AngularJS

How to provide value of $scope variable as string in HTML?

Yeah, This is weird issue.
I am adding code to existing code base where there is a directive
<my-progress ng-progress="processingReport" msg="someString"></my-progress>
The problem is that msg needs to be dereferenced string.
I have a scope variable as $scope.myStatus but providing msg={{myStatus}} is not producing anything
Is there a way to dereference the value of $scope.myStatus so that msg only receives its value?
UPDATE
The directive looks like
.directive('myProgress', function($compile) {
return {
restrict: 'E',
link: function(scope, element, attrs){
var msg = attrs.msg?attrs.msg: "{{"+attrs.ngMsg+"}}";
var template = '<p ng-if="'+ attrs.ngProgress +'"><img src="img/busy-20.gif" alt=""> '+ msg +'</p>';
var el = $compile(template)(scope);
element.replaceWith(el);
}
};
})
UPDATE 1
As per #charlietfl recommendation, the following worked well
In Controller
$scope.runningStatus = {progressStatus: 'Not Started'};
In HTML
<my-progress ng-progress="processingReport" ng-msg="runningStatus.progressStatus"></my-progress>
Based on directive code you should be able to use :
ng-msg="myStatus"
Which would get added into the template as an expression
Note the line in directive:
var msg = attrs.msg?attrs.msg: "{{"+attrs.ngMsg+"}}";
which looks for one or the other attribute and treats them differently
The directive is kind of a hack and should be rewritten, but as is it should accept an interpolated expression:
<my-progress ng-progress="processingReport" msg="{{myStatus}}"></my-progress>
Don't forget the double quotes " around {{myStatus}}
If it still doesn't work, could you test that your scope is okay by adding this test element just after the directive one:
<div>{{myStatus}}</div>

How to pass an actual object (i.e. reference to an object) to a directive?

In my Angular app, I have a few nested ng-repeats to parse through some expected JSON objects, for example:
{
landAnimals: {
horse: {
sound: "Nay",
legs: 4,
},
beaver: {
sound: "thwack",
legs: 2
}
},
waterAnimals: {
dolphin: {
sound: "Eee",
fins: 3,
},
goldfish: {
sound: "-",
fins: 1
}
}
}
At one point, what I would like to do is pass the animal category to my directive and another animal object to it.
For example, if a user drags another animal into the list that is generated in my app, I want to add the animal he dragged into the JSON above.
To do this, I'm trying to pass the animal object to a directive and then adding the new animal to it.
For instance:
<div ng-repeat="animalCategory in animals on-drop-success='animalCategory'">
<div ng-repeat="(key, value) in animalCategory">
{{key}}
</div>
</div>
and then in my onDropSuccess directive, in the link function, I am trying
(don't worry about how I'm doing the drag and drop, it's not working even with this simple test)
...
link: function (scope, element, attrs) {
attrs.onDropSuccess["newAnimal"] = {sound: "miy", legs: 2};
...
To summarize, I am trying to pass the animalCategory object to my directive so I can add more objects under it. But it's not working. It doesn't add the object even when I manually provide a naive object (i.e. it has nothing to do with the drag implementation)
Any ideas why this is happening?
At the moment, you aren't referencing an actual object, only a property on the attrs construct that will return a string.
There are a couple of ways you could pull in an actual reference.
Using scope.$eval
//Target object will be pulled in if it exists on the scope
// this will not work if you are using Isolate Scope
var targetObject = scope.$eval(attrs.onDropSuccess);
Using = parameters on Isolate Scope
You can pull in parameters into the directives isolate scope by using the scope property on the directive definition.
{
scope:{
onDropSuccess: '='
}
link: function(scope, elem, attrs){
//Basically same as above, except that they
// are pulled from the parent scope
var targetObject = scope.onDropSuccess;
}
}

Converting an array to a nested object

What I'm trying to do:
I am trying to dynamically update a scope with AngularJS in a directive, based on the ngModel.
A little back story:
I noticed Angular is treating my ngModel strings as a string instead of an object. So if I have this:
ng-model="formdata.reports.first_name"
If I try to pull the ngModel in a directive, and assign something to it, I end up with $scope["formdata.reports.first_name"]. It treats it as a string instead of a nested object.
What I am doing now:
I figured the only way to get this to work would be to split the ngModel string into an array, so I am now working with:
models = ["formdata", "reports", "first_name"];
This works pretty good, and I am able to use dynamic values on a static length now, like this:
$scope[models[0]][models[1]][models[2]] = "Bob";
The question:
How do I make the length of the dynamic scope dynamic? I want this to be scalable for 100 nested objects if needed, or even just 1.
UPDATE:
I was able to make this semi-dynamic using if statements, but how would I use a for loop so I didn't have a "max"?
if (models[0]) {
if (models[1]) {
if (models[2]) {
if (models[3]) {
$scope[models[0]][models[1]][models[2]][models[3]] = "Bob";
} else {
$scope[models[0]][models[1]][models[2]] = "Bob";
}
} else {
$scope[models[0]][models[1]] = "Bob";
}
} else {
$scope[models[0]] = "Bob";
}
}
This is an answer to
I noticed Angular is treating my ngModel strings as a string instead of an object
Add the require property to your directive then add a fourth ctrl argument to your link function
app.directive('myDirective', function() {
return {
require: 'ngModel',
link: function(scope, element, attributes, ctrl) {
// Now you have access to ngModelController for whatever you passed in with the ng-model="" attribute
ctrl.$setViewValue('x');
}
};
});
Demonstration: http://plnkr.co/edit/Fcl4cUXpdE5w6fHMGUgC
Dynamic pathing:
var obj = $scope;
for (var i = 0; i<models.length-1; i++) {
obj = obj[models[i]];
}
obj[models[models.length-1]] = 'Bob';
Obviously no checks are made, so if the path is wrong it will fail with an error. I find your original problem with angular suspicious, perhaps you could explore a bit in that direction before you resort to this workaround.

Categories