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>'
};
});
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'm trying to build a custom directive in angular; it needs to rielaborate data passed to it before rendering the page, so in the i need to get data passed to my directive through attributes and do some stuff, and finally render the page.
.directive('lpcEdiTable', function($interpolate) {
return {
restrict: "E",
templateUrl: "...",
replace: false,
scope: {
collection: "="
},
link: function(scope, elem, attr) {
//here i need to retrieve data
var myColl = scope.collection; //it's not working
//do some stuff here on myColl
scope.collection = myColl;
}
};
});
so here's how i use the directive:
<lpc-edi-table collection="products"></lpc-edi-table>
where products is a complex object.
in directive template i use the post elaboration data into ng-repeat and other stuff
i tried to follow this but i could not retrieve data into link function
Here is an example of passing an object to a directive
angular.module("myModule", [])
.controller("baseController", ['$scope', function($scope) {
$scope.products = [
"asd",
"asdasd"
];
}])
.directive('myDirective', function() {
return {
restrict: "E",
template: "<p ng-repeat='item in collection'>{{item.attr}}</p>",
scope: {
collection: "="
},
link: function(scope, elem, attr) {
if (!attr.collection) throw new Error("lpc-edi-table directive: 'collection' attribute not found!");
scope.collection = scope.collection.map(function(a) { return {attr: a} });
console.log(scope.collection);
}
};
});
You can call your directive like
<my-directive collection="products"></my-directive>
DEMO https://plnkr.co/edit/cj4oSPRiNo8iYfinIztT?p=preview
For Angular 1.6, I recommend using components. Especially if you don't need to do advanced DOM manipulation.
app.component('lpcEdiTable', {
// $ctrl is controller instance
template: '<div ng-repeat="object in collection">{{object | json}}</div>',
bindings: { //custom attributes
collection: '<' //one way binding. Can also be two way =
},
controller: function(){
//$onInit gets called when the bindings are ready
this.$onInit = function(){
// this.collection is now ready
// safe to manipulate
};
}
});
This can be used like:
<lpc-edi-table collection="products"></lpc-edi-table>
OK, so I have a directive which takes attributes and reads it (and writes it out).
Here is the plunker: http://embed.plnkr.co/IkKPLahPc9yqeHWEQUG3/
I think it's because of the controller: ctrl inside main-directive.js which has nothing whereas the actual action is happening inside the isolated directive's controller controller.
Here is the main-directive.js:
var app = angular.module('testapp.directive.main', ['main']);
app.directive('myCustomer', function() {
var controller = ['$scope', function($scope) {
$scope.dan = { 'name': 'Dan', 'nationality': 'ESP' };
// scope from here obv...
}];
var template = 'Getting attribute value of =getInfo... {{getInfo.name}} from {{getInfo.nationality}}';
return {
restrict: 'E',
controller: controller,
scope: {
getInfo: "=info"
},
template: template
};
});
app.controller('ctrl', function($scope) {
})
and here's my template:
<div ng-controller="ctrl">
<my-customer info="dan">
</my-customer>
</div>
Why is my directive not reading the attribute of info?
You're right, the $scope.dan object needs to be in the ‘ctrl’ controller scope and pulled out of the isolate directives controller scope.
app.controller('ctrl', function($scope) {
$scope.dan = { 'name': 'Dan', 'nationality': 'ESP' };
})
This is applicable to the method of two-way data binding that you have set up for getInfo used by "=info"
The way that is coded, it is expecting the ctrl controller to have a property called "dan" on its scope. If you are just passing in the string 'dan', you want to change your directive to use # instead of =
app.directive('myCustomer', function () {
var controller = ['$scope', function ($scope) {
$scope.dan = {'name': 'Dan', 'nationality': 'ESP'};
// scope from here obv...
}];
var template = 'Getting attribute value of =getInfo... {{getInfo.name}} from {{getInfo.nationality}}';
return {
restrict: 'E',
controller: controller,
scope: {
getInfo: "#info" //<--NOTE THE CHANGE HERE
},
template: template
};
});
In my directive, I'm instantiating an object.
I'd like to pass this object to the scope of the controller I associate with the directive. How do I do that?
Please keep in mind this is an isolated code for you to understand the issue.
In the actual issue it won't help to instantiate that object inside of the controller.
I know that the scope object in the directive is for passing values that are specified in the HTML, I wrote it that way to help you understand what I'm trying to do.
angular.module('test', [])
.controller('test', ['$scope', function($scope) {
alert($scope.obj); //Needs to contain {value: 'bla'}
}])
.directive('pTest', ['$compile', function($compile) {
var object = {value: 'bla'};
return {
scope: {
obj: object //how can I do that?
},
controller: 'test'
};
}]);
You can do this in the link function of the direction. Since you want to set the value on the scope, you can use the scope parameter of the link function. You can also set the object on the controller, since The fourth argument (optional) argument to the link function is the controller for the directive.
.directive('pTest', ['$compile', function($compile) {
var object = {value: 'bla'};
return {
controller: 'test',
link: function(scope, elements, attrs, controller) {
scope.obj = object;
// or
controller.obj = object;
}
};
}]);
Now that assume you don't want to isolate your scope by using a "scope" member in the return of your directive. From your example I don't think you actually want an isolated scope. (Regardless, the link function would work there too.)
You can have two solution
Solution 1: use '=' in isolated scope, it binds a local/directive scope property to a parent scope property.
.directive('ptest', ['$compile', function($compile) {
var object = {value: 'changed value'};
return {
scope: {
iobj:"="
},
template : "<div>{{iobj.value}}<div>",
link: function(scope,elem,attr){
scope.iobj=object ;
}
};
}]);
in html
<div ng-controller="testCtrl">
<div ptest iobj="object"></div>
</div>
Solution 2: use $controller service and make testCtrl as parent and copy its all scope to controllers scope
.directive('ptest', ['$compile', function($compile,$controller) {
var object = {value: 'changed value'};
return {
controller:function($scope,$controller){
$controller('testCtrl', {$scope: $scope});
console.log($scope.object.value);
$scope.object = object;
}
};
}]);
working example for '=' solution 1 :
angular.module('test',[])
.controller('testCtrl',function($scope){
$scope.object = {value:'intial value'};
})
.directive('ptest', ['$compile', function($compile) {
var object = {value: 'changed value'};
return {
//replace:true,
scope: {
iobj:"="
},
template : "<div>{{iobj.value}}<div>",
link: function(scope,elem,attr){
scope.iobj=object ;
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="testCtrl">
{{object.value}}
<div ptest iobj="object"></div>
</div>
Working example for solution 2 with $controller
angular.module('test',[])
.controller('testCtrl',function($scope){
$scope.object = {value:'intial value'};
})
.directive('ptest', ['$compile', function($compile,$controller) {
var object = {value: 'changed value'};
return {
controller:function($scope,$controller){
$controller('testCtrl', {$scope: $scope});
console.log($scope.object.value);
$scope.object = object;
}
};
}]);
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="test" ng-controller="testCtrl">
{{object.value}}
<div ptest ></div>
</div>
I am trying to create a angular tree directive, here is the codes :
//** Tree constructor
var Tree = function() {
return {
restrict: 'EA',
replace: true,
template: "<ul>" +
"<li ng-repeat=\"node in node.children\">" +
"<a ng-click=\"selectNode(node)\" ng-class=\"{selected: isSelected(node)}\">" +
"{{node.name}}" +
"</a>" +
"<tree-children></tree-children>" +
"</li>" +
"</ul>",
scope: {
treeData: '='
},
controller: function($scope) {
//** Selected Node
$scope.selectedNode = null;
//** Node on click
$scope.selectNode = function(node) {
if ($scope.selectedNode !== node) {
$scope.selectedNode = node;
} else {
$scope.selectedNode = null;
}
};
$scope.isSelected = function(node) {
return (node === $scope.selectedNode);
}
},
link: function(scope, elem, attrs) {
//** Watch
scope.$watch('treeData', function(data) {
if (angular.isArray(data)) {
scope.node = {};
scope.node.children = data;
} else {
//***********
}
});
}
}
}
//** Tree children constructor
var TreeChildren = function($compile) {
return {
restrict: 'E',
link: function(scope, elem, attrs) {
var childScope = scope.$new(),
template = '<tree tree-data="node.children"></tree>',
content = $compile(template)(childScope);
//** Append
elem.append(content);
}
};
};
TreeChildren.$inject = ['$compile'];
//** Angular Module
angular
.module('app', [])
.directive('tree', Tree)
.directive('treeChildren', TreeChildren)
.controller('treeCtrl', ['$scope', function($scope) {
$scope.treeData = [
{
name: 'Root',
children: [
{
name: 'Node-1',
children: [
{ name: 'Node-1-1'},
{ name: 'Node-1-2'},
{ name: 'node-1-3'}
]
},
{
name: 'Node-2',
children: [
{ name: 'Node-2-1'}
]
}
]
}
];
}]);
The Plunker link
I have got the problem to set up $scope.selectedNode to be a Global one, now if click on the tree node, the css style doesn't look right, as $scope.selectedNode only affect on it's own scope within the treeChildren directive.
How do I do the scope inheritance from the main directive scope? as I want every node click will access the Global $scope.selectedNode.
I have done some reading on Understanding Scopes but still confuse.
Hope I do explain clearly as my poor english
There are a few things wrong with your code. Instead of going through them, I suggest trying using the alias syntax on your controller.
It will simplify your code and probably clarify what you're trying to do.
The alias syntax avoids injecting $scope directly and makes clearer which controller one is using.
Check this awesome explanation out.
I hope it helps.