Reset scope variable in directive after controller and postLink initialize; - javascript

I create the simple directive:
angular.module('app').directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
if (!$scope.inputId) {
$scope.inputId = 'abc';
}
function logId() {
console.log($scope.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function() {
//$scope.inputId is undefined here
}
}
}
});
Then I use it without inputId field like this:
<field></field>
When I use directive my $scope.inputId is undefined but not 'abc' as I want.
Why? What can I do to get 'abc' if inputId not specified in directive usage?
P.S. The same situation when the code in postLink function.
EDIT:
Plank: http://plnkr.co/edit/0mpcbrdUdafCMzATmCYZ?p=preview
Ugly workaround (to show how it must work): http://plnkr.co/edit/G6gxQhgrteJBeNmR7yTX?p=preview

You can set a default attribute in compile and avoid assigning to the scope in the controller.
compile: function(element, attrs) {
if (!attrs.inputId) { attrs.inputId = 'abc'; }
},
And remove the code setting a default value.

After looking at your plank I would suggest you not to use the direct attribute inputId inside $scope. Instead use properties with '.' object notation as follows:
angular.module('app', []).directive('field', function () {
return {
restrict: 'E',
template: '<div ng-click="clickElement()"><input id="{{inputId || formObj.inputId}}"></div>',
scope: {
inputId: '#'
},
controller: function ($scope) {
$scope.formObj = {};
if (!$scope.formObj.inputId) {
$scope.formObj.inputId = 'abc';
}
function logId() {
console.log($scope.formObj.inputId); // 'abc'
console.log($scope); //$scope.inputId is undefined here (!!!)
}
logId();
$scope.clickElement = function(){
console.log($scope.formObj.inputId);
alert($scope.formObj.inputId);
//$scope.inputId is undefined here but must be 'abc'
}
}
}
}).controller("ctrl", function(){});

Related

Decorating directive with unnamed link function

How can one derocate directive if link function is not named. More specifically I want to decorate select-ui directive. This is how their directive is defined:
.directive('uiSelect', function() {
return {
restrict: 'EA',
scope: true,
compile: function(tElement, tAttrs) {
//some code
return function(scope, element, attrs, ctrls){
//some code here
//how do I extend this?
}
}
}
});
This is how I tried, but it's giving me error Cannot read property 'apply' of undefined, because link function is not named in directive :
.decorator('uiSelectDirective', function($delegate, Restangular){
var directive;
directive = $delegate[0];
console.log(directive, $delegate);
directive.scope = {
endpoint: '=',
items: '=',
parameters: '='
};
directive.compile = function() {
return function($scope, element, attrs){
directive.link.apply(this, arguments);
// custom code here
}
};
return $delegate;
});
EDIT
I tried the suggested approach, but i'm running into issues.
.decorator('uiSelectDirective', function($delegate, Restangular){
var directive;
directive = $delegate[0];
directive.scope = {
endpoint: '=',
items: '=',
parameters: '='
};
directive.compile = function(originalCompile) {
var args = arguments;
return function($scope, element, attrs){
console.log($scope); // this here is returning element instead of scope?
return originalCompile.apply(this, args);
}
}(directive.compile);
return $delegate;
});
Instead of $scope I'm getting element variable ([div.ui-select-container.ui-select-bootstrap.dropdown]). I also have error saying that tAttrs is not defined, which is the parameter in main compule function. I'm guessing that apply isn't passing arguments into function somehow...
Do something this like this to extend compile function:
directive.compile = function(originalCompile) {
return function() {
var oldLink = originalCompile.apply(this, arguments);
return function($scope, element, attrs) {
// custom code for link here
return oldLink.apply(this, arguments)
}
}
}(directive.compile);

Call function in Directive when Parent Scope Variable Changes

I need to call a function in my directive when the value of variable in the parent controller changes. I tried adding a watch (I'm obviously doing it wrong) because nothing happens when the value changes. Here is the directive:
angular.module('ssq.shared').directive('checkboxPicklist', function() {
return {
restrict: 'E',
templateUrl: '/Scripts/app/Shared/directives/checkboxPicklist.html',
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
link: function (scope, element, attrs) {
scope.query = '';
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
var child = element.find('.dropdown-menu');
child.on({
'click': function (e) {
e.stopPropagation();
}
});
var selectedItemFn = function (item) {
return item.selected;
};
scope.getSelectedCount = function () {
return _.filter(scope.list, selectedItemFn).length;
};
scope.loadMore = function () {
scope.callMore();
};
scope.allSelected = function(list) {
var newValue = !scope.allNeedsMet(list);
_.each(list, function(item) {
item.selected = newValue;
scope.callbackFn({ object: item });
});
};
scope.allNeedsMet = function(list) {
var needsMet = _.reduce(list, function(memo, item) {
return memo + (item.selected ? 1 : 0);
}, 0);
if (!list) {
return (needsMet === 0);
}
return (needsMet === list.length);
};
function clearAll() {
_.each(list, function (item) {
item.selected = false;
})
}
}
};
});
Here is where I am trying to watch the variable:
var parent = scope.$parent;
var clear = parent.clear;
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
Here is the function in my parent controller that changes the value of "clear"
$scope.clearFilters = function (clear) {
$scope.clear = true;
$scope.form.selected.services = [];
$scope.form.picked.areas = [];
$scope.form.certified.verifications = [];
$scope.form.subscribed.subscriptions = [];
$scope.form.OperatorBusinessUnitID = null;
$scope.form.OperatorBusinessUnitID = null;
};
I tried setting an attribute called "clearFilter" and assigning the variable to it, but the watch still doesn't trigger:
scope.$watch(attrs.clearFilter, function (value) {
if (value == true) {
this.clearAll();
}
});
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear-filter="clear">
</checkbox-picklist>
I'm not really sure if I am calling the function correctly. scope.$parent above does get the initial value of the variable from the parent scope, but once it changes, it never updates.
EDIT:What I have discovered is the normal scope.$watch('clear', function...) is not working it seems because the directive is in "ssq.shared" module which is injected in my my Main Module "myModule" (see below), so even though the page the directive is on uses my 'GeneralSearchCtrl', I cannot get the watch to work on the variable located in 'GeneralSearchCtrl'. If I use scope.$parent.clear I can see the value of the variable, but I cannot seem to set a watch on it.
My module injection code:
var app = angular.module('myModule', ['ui.bootstrap', 'checklist-model', 'ssq.shared', 'ngAnimate', 'ngTouch', 'ui.grid', 'ui.grid.pagination', 'ui.grid.selection', 'ui.grid.exporter', 'ui.grid.autoResize', 'ui.router', 'cgBusy', 'ui.mask', 'ngFileUpload', 'ngSanitize']);
The page where the directive lives uses:
<div ng-app="myModule" ng-controller="GeneralSearchCtrl">
I am unable to get a watch on the variable located in GeneralSearchCtrl.
Any assistance is greatly appreciated!!!!
Add a watch for the $scope value and call the function,
scope.$watch('clear', function(newValue, oldValue) {
if (newValue) {
this.clearAll();
}
});
scope.$watch(clear, function () {
if (clear == true) {
this.clearAll();
}
})
This.clearAll() doesn't exist in the scope of your $watch function. Simply calling clearAll() should work better.
The signature of the watch function is not correct.
scope.$watch('clear', function (new, old) {}
As it turns out, the problem was that the directive had scope:{...} in its definition which stopped the "normal" scope.$watch('clear', function...) from working. I had to add clear: '=' to the scope list like so:
replace: true,
scope: {
itemId: '=',
list: '=',
nameProp: '=',
title: '#',
searchPlaceholder: '#',
callbackFn: '&',
callMore: '&',
clear: '='
},
Then clear="clear" to the directive like so:
<checkbox-picklist data-item-id="'servicesPicklist'"
data-search-placeholder="Search Services"
data-list="services"
data-title="Service(s)"
data-name-prop="'vchDescription'"
data-callback-fn="addService(object)"
call-more="loadMoreServices()"
clear="clear">
</checkbox-picklist>
Then in the directive I had to add the watch like this for it work:
scope.$watch('$parent.clear', function (newValue, oldValue) {
if (newValue == true) {
clearAll();
alert('it works!');
}
})
I really hope this helps someone else as this was difficult for me to figure out. Happy coding!

Passing a string description of an Angular scope variable to a directive

I am trying to pass a dynamic variable to an angular directive. I can come up w/ a string describing the variable I am after. But I cannot bind that string to the actual Angular scope variable.
my_app.js:
(function() {
var app = angular.module("my_app");
app.controller("MyController", [ "$scope", function($scope) {
$scope.my_data = {
name: "bob",
display_detail: false,
children: [
{
name: "fred",
display_detail: false
},
{
name: "joe",
display_detail: true
}
]
}
app.directive('myDirective', ['$http', function($http) {
return {
restrict: "EAC",
link: function (scope, element, attrs) {
var model = attrs["modelToWatch"];
var watched_name = model + ".display_detail";
scope.$watch(watched_name, function (is_displayed) {
if (is_displayed) {
// do something clever w/ $http...
}
}
}
}
}]);
my_template.html:
<div ng-app="my_app">
<div ng-controller="MyController">
<my_directive model_to_watch="my_data.children[1]"/>
</div>
</div>
The idea is that the string "my_data.children[1]" gets passed to the directive. My goal is to somehow evaluate this and wind up with the actual model (the one named "joe" above).
Why not just pass it into the directive's scope?
app.directive('myDirective', ['$http', function($http) {
return {
scope: {
watchedName: '='
},
restrict: "EAC",
link: function (scope, element, attrs) {
scope.$watch(scope.watchedName.display_detail, function (is_displayed) {
if (is_displayed) {
// do something clever w/ $http...
}
}
}
}
and
<my-directive watched-name="my_data.children[1]"/>
Generally, if you have a string and want to get the scope value corresponding to it, you can use $eval on $scope to get that information. So, in your case, something like this might work:
link: function (scope, element, attrs) {
var model = attrs["modelToWatch"];
var watched_name = model.display_detail;
scope.$watch(scope.$eval(watched_name), function (is_displayed) {
if (is_displayed) {
// do something clever w/ $http...
}
}
}
You can use $scope.$parent.$watch and pass the attrs value as the first argument, i.e.
app.directive('myDirective', ['$http', function($http) {
return {
restrict: "EAC",
link: (scope, elem, attrs) => {
scope.$parent.$watch(attrs["modelToWatch"], function(newVal) {
if (newVal) {
// fancy http ajax method here
}
});
}
};
});

Custom directive - two way binding not working

I am trying to write a simple custom directive in Angular that turns a tag into a toggle button (similar to a checkbox). The code I have written so far updates the internal variable (isolated scope) but the two way binding doesn't seem to work. When I click the button, the button toggles (the css class is appearing and disappearing) but myVariable is not updating.
Any help much appreciated!
Usage
<button toggle-button="myVariable">My Button</button>
Directive code
( function() {
var directive = function () {
return {
restrict: 'A',
scope: {
toggleButton: '=checked'
},
link: function( $scope, element, attrs ) {
$scope.$watch('checked', function(newVal, oldVal) {
newVal ? element.addClass ('on') : element.removeClass('on');
});
element.bind('click', function() {
$scope.checked = !$scope.checked;
$scope.$apply();
});
}
};
};
angular.module('myApp')
.directive('toggleButton', directive );
}());
just replace
scope: {
toggleButton: '=checked'
}
to
scope: {
checked: '=toggleButton'
}
Your directive scope is looking for an attribute that doesn't exist.
Try changing:
scope: {
toggleButton: '=checked'
},
To
scope: {
toggleButton: '='
},
The difference is that =checked would look for the attribute checked whereas = will use the same attribute as the property name in the scope object
Will also need to change the $watch but you could get rid of it and use ng-class
As charlietfl said, you don't need that checked variable. You are making changes to it instead of the external variable.
Here is a fixed version:
angular.module('components', [])
.directive('toggleButton', function () {
return {
restrict: 'A',
scope:{
toggleButton:'='
},
link: function($scope, $element, $attrs) {
$scope.$watch('toggleButton', function(newVal) {
newVal ? $element.addClass ('on') : $element.removeClass('on');
});
$element.bind('click', function() {
$scope.toggleButton = !$scope.toggleButton;
$scope.$apply();
});
}
}
})
angular.module('HelloApp', ['components'])
http://jsfiddle.net/b3b3qkug/1/

directive not working angularjs

The directive does not work from the controller. how to fix it?
baseapp.directive('loading', function () {
alert('loading');
return {
restrict: 'E',
replace: true,
template: '<div class="loading">loading</div>',
link: function (scope, element, attr) {
scope.$watch('loading', function (val) {
if (val) {
element.addClass('show');
alert('show');
} else {
element.addClass('hide');
alert('hide');
}
});
}
}
});
baseapp.controller ('ListCtrl', function ($scope, $http) {
$scope.loading = true;
$http.get('/blog').success(function(data) {
$scope.users = data;
$scope.loading = false;
});
});
When you load a directive called. from the controller $ scope.loading = true;
Nothing happens
What I noticed is that your adding a class over and over element.addClass('show'); resulting in if true and later false: class='show hide show hide ...' and so forth, one quick way to correct this is to remove one of the classes or you can toggle class:
.directive('myDir', function() {
return {
restrict: 'E',
replace: true,
template: '<div class="loading">loading</div>',
link: function (scope, element, attr) {
scope.$watch('loading', function (val) {
if (val===true) {
element.addClass('show');
element.removeClass('hide');
} else {
element.toggleClass('hide');
element.removeClass('show');
}
});
}
}
});
Other than that it seems to be working just fine on my end:
Online Demo
Note: I recommend you not to use alerts for debugging use console.log instead.
If you read the official documentation: https://docs.angularjs.org/guide/directive
You can read this:
The restrict option is typically set to:
'A' - only matches attribute name
'E' - only matches element name
'C' - only matches class name
If you want to use it as a class like you do, then you have to specify :
require: 'C'

Categories

Resources