I have a directive with an isolated-scope and one-way binding variable.
yet when i change that variable in the directive controller it updates the parent scope as well.
Example code:
function someDirective() {
return {
restrict: 'E',
replace: true,
scope: {},
bindToController: {
parentVar: '<'
},
templateUrl: templateUrl,
controller: directiveController,
controllerAs: 'vm'
}
}
function directiveController($scope) {
var vm = this;
$scope.$watchCollection('vm.parentVar', doSomething);
function doSomething(newCollection) {
var some_object = {
property1: 1,
property2: 2
};
newCollection.unshift(some_object);
}
}
After I update the passed variable in the directive, I see some_object in other parts of my app.
Thank you.
parentVar is an array reference, so the items that to add to it can be accessed from both parent controller.
If you do not want the changes from directive controller to be reflected, you will have to clone the array before you operate on it.
function directiveController($scope) {
var vm = this;
$scope.$watchCollection('vm.parentVar', doSomething);
function doSomething(newCollection) {
var clonedCollection = newCollection.slice();
var some_object = {
property1: 1,
property2: 2
};
clonedCollection.unshift(some_object);
}
}
Related
I have a directive with isolated scope. I am modifying one of the variables passed from the parent controller in the controller of the directive. The issue I'm running into is that when I use multiple instances of this directive (with different options and model) on the same view, the options object does not remain unique to each instance of the directive. Instead, it becomes a shared variable and all the instances of the directive use the same options object.
So if I had used them in my view like below, with optionsA.isFlagOn = true and optionsB.isFlagOn = false
<my-directive model="modelA" options="optionsA">
<my-directive model="modelB" options="optionsB">
Directive with modelB loads with the optionsA.
How do I keep options unique while modifying it for each specific instance?
angular.module('myModule', [])
.directive('myDirective', function($compile) {
template = '<h3><span ng-bind="model.title"><h3><p><span ng-bind="options"></span></p>';
return {
restrict: 'AE',
scope: {
model: "=",
options: "=?" //A JSON object
},
controller: function($scope) {
$scope.options = $scope.options || {};
//A function that sets default values if no options object passed
ensureDefaultOptions($scope);
//now based on some of the options passed in, I modify a property in the options object
if ($scope.options.isFlagOn)
$scope.options.thisProp = true;
},
link: function(scope, element, attr) {
let content = $compile(template)(scope);
element.append(content);
}
};
}
Edit: I solved my issue. My solution is posted in the answer below.
Can you change it to you one-way bindings with:
scope: {
model: "=",
options: "<?" //A JSON object
}
Your directive should make a copy of passed in options combined with defaults, so each directive instance have its own option object.
You can achieve that easily by using extend
var defaultOptions = { a:1, b:2, c:3 };
var options = angular.extend(defaultOption, $scope.options);
// then use options everywhere
Note that this will only be done once during init, so if your options come from controller asynchronously, you'll need extra handling.
I solved it using the bindToController property of Angular directive available in 1.4x or higher.
angular.module('myModule', [])
.directive('myDirective', function($compile) {
template = '<h3><span ng-bind="vm.model.title"><h3><p><span ng-bind="myOptions"></span></p>';
return {
restrict: 'AE',
bindToController: {
model: "=",
options: "=?" //A JSON object
},
scope: {},
controller: function() {
var vm = this;
//a function that handles modifying options
vm.setOptions = function(options){
let newOptions = {};
angular.copy(options, newOptions);
// modify newOptions here
return newOptions;
}
},
controllerAs: 'vm',
link: function(scope, element, attr) {
ensureDefaultOptions(scope.vm);
scope.myOptions = scope.vm.setOptions(scope.vm.options);
let content = $compile(template)(scope);
element.append(content);
}
};
});
I will explain what exactly I'm trying to do before explaining the issue. I have a Directive which holds a form, and I need to access that form from the parent element (where the Directive is used) when clicking on a submit button to check fi the form is valid.
To do this, I am trying to use $scope.$parent[$attrs.directiveName] = this; and then binding some methods to the the Directive such as this.isValid which will be exposed and executable in the parent.
This works fine when running locally, but when minifying and building my code (Yeoman angular-fullstack) I will get an error for aProvider being unknown which I traced back to a $scopeProvider error in the Controller.
I've had similar issues in the past, and my first thought was that I need to specifically say $inject for $scope so that the name isn't lost. But alas.....no luck.
Is something glaringly obvious that I am doing wrong?
Any help appreciated.
(function() {
'use strict';
angular
.module('myApp')
.directive('formDirective', formDirective);
function formDirective() {
var directive = {
templateUrl: 'path/to/template.html',
restrict: 'EA',
scope: {
user: '='
},
controller: controller
};
return directive;
controller.$inject = ['$scope', '$attrs', 'myService'];
function controller($scope, $attrs, myService) {
$scope.myService = myService;
// Exposes the Directive Controller on the parent Scope with name Directive's name
$scope.$parent[$attrs.directiveName] = this;
this.isValid = function() {
return $scope.myForm.$valid;
};
this.setDirty = function() {
Object.keys($scope.myForm).forEach(function(key) {
if (!key.match(/\$/)) {
$scope.myForm[key].$setDirty();
$scope.myForm[key].$setTouched();
}
});
$scope.myForm.$setDirty();
};
}
}
})();
Change the directive to a component and implement a clear interface.
Parent Container (parent.html):
<form-component some-input="importantInfo" on-update="someFunction(data)">
</form-component>
Parent controller (parent.js):
//...
$scope.importantInfo = {data: 'data...'};
$scope.someFunction = function (data) {
//do stuff with the data
}
//..
form-component.js:
angular.module('app')
.component('formComponent', {
template:'<template-etc>',
controller: Controller,
controllerAs: 'ctrl',
bindings: {
onUpdate: '&',
someInput: '<'
}
});
function Controller() {
var ctrl = this;
ctrl.someFormThing = function (value) {
ctrl.onUpdate({data: value})
}
}
So if an event in your form triggers the function ctrl.someFormThing(data). This can be passed up to the parent by calling ctrl.onUpdate().
I have a directive treeview which contains a nested directive (being the branches) of each item rendered.
In the scope of both directives I have declared two parameters that should be talking to the parent controller.
filter: '&' //binds the method filter within the nested directive (branch) to the method doSomething() in the tree directive attribute which is bound to the html directive that binds to the controller.
iobj: '=' is the two way binding paramter that should be passing the scoped object to the controller. (but currently isn't)
Directive:
app.directive('tree', function () {
return {
restrict: 'E',
replace: true,
scope: {
t: '=src',
filter: '&',
iobj: '='
},
controller: 'treeController',
template: '<ul><branch ng-repeat="c in t.children" iobj="object" src="c" filter="doSomething()"></branch></ul>'
};
});
app.directive('branch', function($compile) {
return {
restrict: 'E',
replace: true,
scope: {
b: '=src',
filter: '&',
iobj: '='
},
template: '<li><input type="checkbox" ng-click="innerCall()" ng-hide="visible" /><a>{{ b.name }}</a></li>',
link: function (scope, element, attrs) {
var has_children = angular.isArray(scope.b.children);
scope.visible = has_children;
if (has_children) {
element.append('<tree src="b"></tree>');
$compile(element.contents())(scope);
}
element.on('click', function(event) {
event.stopPropagation();
if (has_children) {
element.toggleClass('collapsed');
}
});
scope.innerCall = function () {
scope.iobj = scope.b;
console.log(scope.iobj);
scope.filter();
}
}
};
});
HTML:
<div ng-controller="treeController">
<tree src="myList" iobj="object" filter="doSomething()"></tree>
<a ng-click="clicked()"> link</a>
</div>
Controller:
app.controller("treeController", ['$scope', function($scope) {
var vm = this;
$scope.object = {};
$scope.doSomething = function () {
var item = $scope.object;
//alert('call from directive');
console.log(item);
}
$scope.clicked = function () {
alert('clicked');
}
...
Currently I can invoke the function $scope.doSomething from the directive to the controller. So I know that I have access to the controllers scope from the directive. What I cannot figure out is how to pass an object as a parameter from the directive back to the controller. When I run this code, $scope.object
is always an empty object.
I'd appreciate any help or suggestions on how to go about this.
The & directive binding supports parameter passing. Given your example
scope.filter({message: 'Hello', anotherMessage: 'Good'})
The message and anotherMessage become local variables in the expression bound to directive:
<tree src="myList" iobj="object" filter="doSomething(anotherMessage, message)"></tree>
Here's a sample plunker where the callback parameters are set inside a template.
The documentation clearly states that:
Often it's desirable to pass data from the isolated scope via an
expression to the parent scope, this can be done by passing a map of
local variable names and values into the expression wrapper fn. For
example, if the expression is increment(amount) then we can specify
the amount value by calling the localFn as localFn({amount: 22}).
I wanted to use a directive to have some click-to-edit functionality in my front end.
This is the directive I am using for that: http://icelab.com.au/articles/levelling-up-with-angularjs-building-a-reusable-click-to-edit-directive/
'use strict';
angular.module('jayMapApp')
.directive('clickToEdit', function () {
return {
templateUrl: 'directives/clickToEdit/clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I added a second attribute to the directive to call a method after when the user changed the value and then update the database etc. The method (´$onSave´ here) is called fine, but it seems the parent scope is not yet updated when I call the method at the end of the directive.
Is there a way to call the method but have the parent scope updated for sure?
Thanks in advance,
Michael
I believe you are supposed to create the functions to attach inside the linking function:
Take a look at this code:
http://plnkr.co/edit/ZTx0xrOoQF3i93buJ279?p=preview
app.directive('clickToEdit', function () {
return {
templateUrl: 'clickToEdit.html',
restrict: 'A',
replace: true,
scope: {
value: '=clickToEdit',
method: '&onSave'
},
link: function(scope, element, attrs){
scope.save = function(){
console.log('save in link fired');
}
},
controller: function($scope, $attrs) {
$scope.view = {
editableValue: $scope.value,
editorEnabled: false
};
$scope.enableEditor = function() {
$scope.view.editorEnabled = true;
$scope.view.editableValue = $scope.value;
};
$scope.disableEditor = function() {
$scope.view.editorEnabled = false;
};
$scope.save = function() {
console.log('save in controller fired');
$scope.value = $scope.view.editableValue;
$scope.disableEditor();
$scope.method();
};
}
};
});
I haven't declared the functions inside the controller before, but I don't see why it wouldn't work.
Though this question/answer explain it Link vs compile vs controller
From my understanding:
The controller is used to share data between directive instances, not to "link" functions which would be run as callbacks.
The method is being called but angular doesn't realise it needs to run the digest cycle to update the controller scope. Luckily you can still trigger the digest from inside your isolate scope just wrap the call to the method:
$scope.$apply($scope.method());
I have a specific scenario for a AngularJS directive:
Normally the directive should inherit the default scope
But for some specific scenarios I'd like to replace all values in $scope.myValues with myValues (object loaded from a web-service)
I cannot change in this scenario the main-scope because this is owned by another application (more or less a plugin-mechanism).
Thanks & Regards
Stefan
If think I have found the solution:
Sample Html:
<wi-view data-layout="{{passLayout}}"></wi-view>
<hr />
Original property: {{layout.property1}}
Sample Controller:
app.controller('wiController', function($scope) {
// Simulating the original scope values
$scope.layout = {};
$scope.layout.property1 = 'Original Value';
// New scope values, just here for binding it to the controller
var passLayout = {};
passLayout.property1 = 'Value Overwritten';
passLayout.property2 = 'Another Property';
$scope.passLayout = passLayout;
});
Sample Directive:
app.directive('wiView', function () {
var linkFunction = function(scope, elems, attrs) {
if (attrs.layout !== undefined) {
scope.layout = angular.fromJson(attrs.layout);
}
};
return {
restrict: "E",
scope: true,
priority: 0,
link: linkFunction,
template: '<div>Hello, {{layout.property1}}!</div>'
};
});