Directive not triggering on change of attibute - javascript

I have a 2 dropdowns from which user makes selection. And based on those values, the attributes are sent to directives and corresponding function should get called.
The problem is that the directives are not called on the change of value.
Here is the html code:
<div ng-controller=visCtrl>
<select ng-model="selectedDistrict" ng-options="item as item for item in districts " ng-init="selectedDistrict='GUNTUR'"></select>
<select ng-model="selectedYear" ng-init="selectedYear='2005'" ng-options="item as item.YEAR for item in opendata | filter:{ DISTRICT: selectedDistrict }"></select>
<div ng-repeat="item in categories">
<div id="containervis-item" ng-init="init(item.value)" class="container col-sm-12">
<div scatter-chart city-name={{selectedDistrict}} crime-name={{item}} current-year={{selectedYear}} vis-file=dataFile id="divVisScatter-{{item}}" class="divVis divVisScatter col-sm-6">
{{item}}
</div>
<div linear-chart city-name={{selectedDistrict}} crime-name={{item}} vis-file=dataFile class="divVis divVisLine col-sm-6"></div>
</div>
</div>
</div>
Here is the directive code:
myApp.directive('scatterChart', function($window){
return{
restrict:'EA',
template:"<svg width='850' height='200'></svg>",
link: function(scope, elem, attrs){
var d3 = $window.d3;
var rawSvg=elem.find('svg');
var svg = d3.select(rawSvg[0]);
var cityName=attrs.cityName;
var crime=attrs.crimeName;
var year=attrs.currentYear;
console.log(cityName);//doesnt get called when selection is made
}
};
});
The the directive is called with initial values only. Also, upon making a selection, the DOM gets changed and the city-name and crime-name attributes get new value, but the function isnt called.

By 'the corresponding function should get called' you mean link function? Link function is executed once, after the compilation phase and it is the right place to attach event listeners to HTML template, etc. Check here for more details.
In case you want to execute a function everytime an attribute's value changes, you have to $watch it, and include all the necessary code into $watch function.
eg.
scope.$watch(function() {
return attrs.cityName;
}, function(value) {
console.log(value);
})

Related

How to get a variable attribute of an HTML element?

I have a ng-repeat in which I have set an attribute like so:
<div ng-repeat="item in itemsList"
stepType="{{item.stepType}}">
{{item.itemValue}}
</div>
item.stepType can have values 'task' or 'action'
I'm trying to get the attribute 'stepType' like so:
let stepType = el.getAttributeNode("stepType");
console.log(stepType.value);
The result i'm getting is:
{{item.stepType}}
But the expected result is task or action
I also tried to get the attribute using the function getAttribute() but the result is the same.
I also noticed that in the above code if i log the value of stepType instead of stepType.value, i get the object which looks like this stepType: 'task'
How can i get the expected value?
If the attribute name is normalized:
<div ng-repeat="item in itemsList"
step-type="{{item.stepType}}">
{{item.itemValue}}
</div>
A custom AngularJS directive can $observe the attribute:
app.directive("stepType", function() {
return {
link: postLink
};
function postLink(scope, elem, attrs) {
attrs.$observe("stepType", function(value) {
console.log(value);
});
}
})
The console will log changes to the interpolated value of the attribute.
Try removing the double quotes. Replace this
<div ng-repeat="item in itemsList"
stepType="{{item.stepType}}">
{{item.itemValue}}
</div>
with
<div ng-repeat="item in itemsList"
stepType={{item.stepType}}>
{{item.itemValue}}
</div>

Cannot interpolate vm.value in template - AngularJS

I am updating and modifying a project using Angular JS 1.2.25.
I have my controller where I have a value called vm.stageValue which is then called in template with an ng-if, so when the vm.stageValue increments it shows different containers. But whhen I define a value on the vm object that I want to interpolate on the template, eg a string that will be used and will not change on the template, I cannot seem to get it display.
This has makes me think I have not set up my controller correctly using the vm method.
It seems weird that I can use the ng-if and call function from the controller using ng-click on the template but I cannot interpolate a string or send it to another child component
Code is below, thank you in advance. Any help would be hugely appreciated
Controller
angular
.module('formModule')
.controller('NewBusinessFormCtrl', [
function() {
let vm = this;
// Methods used in controller
vm.methods = {
incrementStageValue: incrementStageValue,
decrementStageValue: decrementStageValue,
canIncrement: canIncrement,
canDecrement: canDecrement
};
//Initial stage values
vm.stageValue = 1;
vm.maxStageValue = 7;
// This is the string that I want to interpolate below
vm.contactFormCategory = 'New Business';
}
]);
Template of Controller
<div class="new_busines_cf" ng-controller="NewBusinessFormCtrl as vm">
<div class="form_wrapper">
<div ng-if="vm.stageValue == 1">
<input-text
class="form_input"
ng-model="ngModel"
input-text-label="This is the label">
</input-text>
// I want to send the vm.contactFormCategory into the component
// Value is sending but the component display 'vm.contactFormCategory'
// Not the value set in the controller
<form-headline
form-headline-sup-title="vm.contactFormCategory"
form-headline-text="This is a form headline text">
</form-headline>
</div>
// Trying to interpolate value here into template, but nothing display
{{vm.contactFormCategory}}
<div ng-if="vm.stageValue == 2">
<input-text
class="form_input"
ng-model="ngModel"
input-text-label="This is the label of stage 2">
</input-text>
<form-headline
form-headline-sup-title="vm.contactFormCategory"
form-headline-text="This is a form headline text">
</form-headline>
</div>
<button ng-click="vm.methods.incrementStageValue()">Increment Value</button>
<button ng-click="vm.methods.decrementStageValue()">decrement Value</button>
</div>
</div>
** Form Headline **
angular
.module('formModule')
.directive('formHeadline', function() {
return {
restrict: 'E',
templateUrl: '/partials/form/form-headline.component.html',
scope: {
formHeadlineText: '#',
formHeadlineSupTitle: '#'
},
link: function () {
}
};
});
Change your ng-if to
<div ng-if="vm.stageValue === '2'">

AngularJS: wait that context is loaded before write HTML

When the input select is loaded in an HTML form, sometimes the data get from the back-end is not ready and the select is displayed without any option selected.
Could be possible to wait that the data is loaded before write the input select in the page?
or there are any other way to select the right option depending on the angular value.
PS. i can't change the data that i get from the back-end and that are una array for the all value and another variable with the selected option. The first one is always loaded correctly but sometimes the second one is empty when i want to select an option.
thanks
I assume you're using asynchronous methods to load the data. In such case, the following should work.
First, have such markup:
<div ng-show="loading">
Loading, please wait...
<!-- can also put gif animation instead -->
</div>
<select ng-hide="loading">...</select>
And in the controller:
$scope.loading = true;
GetData().then(function() {
$scope.loading = false;
}, function() {
$scope.loading = false;
alert('error');
});
This assumes you load the data in a function that returns a Promise, you can of course just put the $scope.loading = false; line in the proper location in your code, after the data is actually loaded.
The effect will be that while $scope.loading is set to true, the user will see the "Loading" message while the drop down is hidden, and when you set it to false, the drop down will become visible while the "Loading" message will become hidden.
Try to get access after event stateChangeSuccess
$scope.$on('$stateChangeSuccess', function() {
(function() {
})();
});
That is how I fix this problem using AngularJS, Angular Resource & Ui-router to display selected object in an entity with Relationship:
Given that we have to entity in a simple relationship:
Class: name(String), level(String). ----> A class in school.
Child: name(String), pseudo(String). ----> A Child.
A child can be in one class at a time and there is many classes in school.
So We can have something like this(a One-To-One):
Class: name(String), level(String). ----> A class in school.
Child: name(String), pseudo(String), class(Class). ----> A Child.
In my Ui-router state I do something like this when editing a Child:
That is the state of the child to edit, when click on a link corresponding to it we query him and use a controller to resolve the entity related to him.
.state('child-edit', {
parent: 'entity',
url: '/child/{id:int}',
views: {
'content#': {
templateUrl: 'path/to/chil/view/child-edit.html',
controller: 'ChildEditController'
}
},
resolve: {
translatePartialLoader: ['$translate', '$translatePartialLoader', function ($translate, $translatePartialLoader) {
$translatePartialLoader.addPart('child');
return $translate.refresh();
}],
entity: ['$stateParams', 'ChildService', function($stateParams, ChildService) {
// We return the child to edit using a service.
return ChildService.get({id : $stateParams.id});
}]
}
})
That is the controller I use to make this run normally:
angular.module('myApp').controller('ChildEditController',
['$scope', '$stateParams', '$q', 'entity', 'ClassService',
function($scope, $stateParams, $q, entity, ClassService) {
// We get all classes of school here.
$scope.classes = ClassService.query();
// That is the promise of child to edit get from resolve in state.
$scope.childToEdit = entity;
$q.all([$scope.classes.$promise, $scope.childToEdit.$promise]).then(function() {
// When all data are resolved
// In Js two objects with same properties and valyes but different memory allocation are different.
// So I test value of Id before setting the right class of this child and angular will make able to edit
// him in the UI with the ng-model
var classOfChild = $scope.childToEdit.class;
for (var k in $scope.classes) {
if ($scope.classes[k].id === classOfChild.id) {
// We put the same reference of this class: then it will be selected in the UI of select box
$scope.childToEdit.class = $scope.classes[k];
}
}
});
}]);
And the associated UI in HTML:
<!-- The name of Child -->
<div class="form-group">
<div class="col-md-4">
<label for="field_child_name">Name of Child</label>
<input type="text" class="form-control" name="name" id="field_child_name"
ng-model="childToEdit.name"
required />
</div>
</div>
<!-- Selected class of child will be display here with all other classes available -->
<div class="form-group">
<div class="col-md-4">
<label for="field_child_class">Class of Child</label>
<select class="form-control" id="field_child_class" name="class" ng-model="childToEdit.class" ng-options="class as class.name + ' : ' + class.level for class in classes">
<option value=""></option>
</select>
</div>
</div>
Note: Hope it is the same situation where the selected data is not displaying because the references of querying class and property class in child object are different.

Directive doesn't fire after changing textarea model

I have a text with newline separator and URLs:
first row\nFind me at http://www.example.com and also\n at http://stackoverflow.com.
I want to update ng-repeat values after pressing on copy button.
I have this HTML:
<div ng-controller="myCntrl">
<textarea ng-model="copy_note_value"></textarea>
<button data-ng-click="copy()">copy</button>
<div>
<p ng-repeat="row in note_value.split('\n') track by $index"
wm-urlify="row"
style="display: inline-block;"
>
</p>
</div>
</div>
Controller:
app.controller('myCntrl', function ($scope) {
$scope.note_value = "first row\nFind me at http://www.example.com and also\n at http://stackoverflow.com";
$scope.copy_note_value = angular.copy($scope.note_value);
$scope.copy = function(){
$scope.note_value = angular.copy($scope.copy_note_value);
}
});
I have directive that should take text and return urlfied text:
app.directive('wmUrlify', ['$parse', function ($parse) {
return {
restrict: 'A',
scope: true,
link: function (scope, element, attrs) {
function urlify(text) {
var urlRegex = /(https?:\/\/[^\s]+)/g;
return text.replace(urlRegex, function (url) {
return '' + url + '';
})
}
var text = $parse(attrs.wmUrlify)(scope);
var html = urlify(text);
element[0].inneHtml(html)
}
};
}]);
Here is a flow: User changes text in textarea and presses on copy button. I expect to show the change in ng-repeat.
It works only if I add a new line and not line content.
What is wrong here? This is my Fiddle
Just remove the track by $index from your ng-repeat. This is because you are telling Angular that the value of note_value.split('\n') will only be changed when there is a change in the $index i.e. size of the array after splitting by new line.
But the default implementation of track by is the identity of each item. So when you changed the default implementation to track it by the $index and when you are not not adding a new line instead just updating the content of any existing line, Angular is not able to detect that there is a change.
Update
Removing the track by $index function will throw an exception when there are same values after split. So you can use a simple function like: (define it in your controller)
$scope.indexFunction = function($index, val) {
// Creating an unique identity based on the index and the value
return $index + val;
};
And then use it in your ng-repeat like:
<p ng-repeat="row in note_value.split('\n') track by indexFunction($index, row)"></p>
https://docs.angularjs.org/api/ng/directive/ngRepeat

Angularjs- adding/removing dynamic html elements (dropdown)

here is my code-
http://plnkr.co/edit/oTWXbLIKOxoGTd4U0goD?p=preview
why is the days dropdown does not data bind with scope.demoDays, it is always empty?
is this the correct way to add dropdown dynamically? If user adds 5 dropdown, how to get the results , will ng-model="selectedDay" create an array of selection? any suggestions?
Thank you
var app = angular.module('plunker', []);
app.controller('MainCtrl', function($scope, $compile) {
var counter = 0;
$scope.fields = [];
$scope.days =['Day','Sun','Mon','Tue','Wed','Thu','Fri','Sat'];
$scope.addField = function() {
$scope.fields.push({name:"test " + counter++});
};
});
app.directive('demoDisplay', function($compile){
return {
scope:{
demoDisplay:"=", //import referenced model to our directives scope
demoDays:"="
},
link:function (scope, elem, attr, ctrl)
{
scope.$watch('demoDisplay', function(){ // watch for when model changes
elem.html("") //remove all elements
angular.forEach(scope.demoDisplay, function(d){ //iterate list
var s = scope.$new(); //create a new scope
angular.extend(s,d); //copy data onto it
console.log(scope.demoDays);
var template = '<label class="item item-input"><div class="style-select"><select ng-model="selectedDay" ng-options="day for day in scope.demoDays"></select><br></div></label>';
elem.append($compile(template)(s)); // compile template & append
});
}, true) //look deep into object
}
}
})
html
<button ng-click="addField()">Add Field</button>
<div demo-display="fields" demo-days="days"></div>
There is no need for $watch in your link function - you have already established two-way binding by specifying = on your scope property. And you can use a plain template, without having to compile.
templateUrl: 'template.html',
where template.html is:
<label class="item item-input">
<div class="style-select">
<select ng-model="demoDisplay.selection" ng-options="day for day in demoDays"></select>
<br>
</div>
</label>
Notice that the select is bound to demoDisplay.selection, which will be created on each field and be accessible on the parent scope via two-way binding. Also, note that within ng-options, I changed scope.demoDays to just demoDays. In a directive's template you only need to use the property's name to access a scope value.
You can use the directive inside ng-repeat to create additional fields when the button is clicked:
<div ng-repeat="field in data.fields">
<div demo-display="field" demo-days="days"></div>
</div>
Here is a working plunker: http://plnkr.co/edit/pOY0l18W7wEbfSU7DKw2?p=preview
Any easy fix to get it working.
In your var template you have scope.demoDays.
Simply change this to demoDays. You are already in this scope so using it again isn't necessary.

Categories

Resources