It is possible to define in which scope's property must assign the attributes defined in a directive?
e.g:
angular.module("myMod",[]).
directive("myDir", {
restrict: "E",
scope: {
prop1: "#myProp1",
prop2: "#myProp2",
},
controller: function($scope){
//Here I have prop1 and prop2 assigned to $scope
$scope.prop1;
$scope.prop2;
},
});
But what I want is something like:
angular.module("myMod",[]).
directive("myDir", {
restrict: "E",
scope: {
config: { prop1: "#myProp1" }, //This is invalid
prop2: "#myProp2",
},
controller: function($scope){
//And here, $scope.config.prop1 refers to myProp1
$scope.config.prop1;
$scope.prop2;
},
});
I found a partial solution: using controllerAs and bindToController, controllerAs defines an alias to refer the controller and bindToController binds the isolated scope properties to the controller, then I have:
$scope.alias.prop1
$scope.alias.prop2
But I don't want to bind all properties to controller, because I don't need that. I want to bind some of them to a scope's property, and the rest to another property or directly to the scope.
Why I'm trying to do this?
Because I want to assign:
$scope.config = newConfig;
That is easier than:
$scope.prop1 = newProp1;
$scope.prop2 = newProp2;
I don't have strong idea but just an idea. What about to use something like this?
var cfg = {prop1: '#myProp1'};
angular.module("myMod",[]).
directive("myDir", {
restrict: "E",
scope: {
config: cfg.prop1,
prop2: "#myProp2",
},
controller: function($scope){
$scope.config;
$scope.prop2;
},
});
I don't sure if it is possible to do exactly as you wish (github angular), but may be it would fit something like this:
angular.module("myMod", []).directive("myDir", function () {
return {
restrict: "E",
scope: {
config: "&myConfig",
prop2: "#myProp2"
},
link: function($scope){
console.log($scope.config(), $scope.prop2);
}
}
});
So in HTML you will be able to define config:
<div ng-app="myMod">
<my-dir my-config='{config1: "conf1Val", config2: "conf2Val"}' my-prop2="prop2Val">wver</my-dir>
</div>
http://jsfiddle.net/xrvpoe4r/2/
Another way is to define convention, for example each config should start with "config-" , and make directive, that will create a new field (object) in scope based on such attributes
For example:
http://jsfiddle.net/xrvpoe4r/4/
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 using the require attribute in directive to create a link between 2 controllers, everything works great but when I want to invoke a parent function from the child scope I need to use the ugly syntax of $parent.ParentCtl.func() and I would like to know if I can avoid this syntax and invoke the function without explicitly writing the $parent notation.
The child doesn't have isolated scope.
I've look around and didn't find an answer to that question.
At the moment I'm using a factory to bind that functions from parent.
Thanks
The good way
If you're using Angular >=1.5 you can use the require syntax in combination with the controllerAs syntax, so instead of using the scope to access the parent, you use a direct reference to the parent which is created by angular, when you explicitly require another directive. For example:
angular.module("test", [])
.directive("foo", function() {
return {
restrict: "E",
scope: {},
bindToController: true,
controllerAs: "fooController",
controller: function() {
var controller = this;
controller.something = "Foo Initial Value";
controller.setSomething = function(something) {
controller.something = something;
console.log("Foo Changed:" + something);
}
}
}
})
.directive("bar", function() {
return {
scope: {},
restrict: "E",
require: {
"parent": "^^foo"
},
controllerAs: "barController",
template: "Bar <a href='' ng-click='barController.parent.setSomething(\"from template\")'>Change directly on parent</a>",
bindToController: true,
controller: function() {
var controller = this;
this.$onInit = function() {
// We can access the parent after the $onInit.
console.log(controller.parent.something);
}
}
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.js"></script>
<body ng-app="test">
<foo>
<bar></bar>
</foo>
</body>
In this example, the bar directive requires the foo controller, which will get binded to the parent property of the bar controller.
The Not-So-Good way
I do not like this way, since the directive's are tied together by the coincidence of they being used in the proper order.
angular.module("test", [])
.directive("foo", function() {
return {
restrict: "E",
scope: true,
controller: ["$scope", function($scope) {
$scope.something = "Foo Initial Value";
$scope.setSomething = function(something) {
$scope.something = something;
console.log("Foo Changed:" + something);
}
}]
}
})
.directive("bar", function() {
return {
scope: true,
restrict: "E",
template: "Bar <a href='' ng-click='setSomething(\"from template\")'>Change directly on parent</a> Something: {{something}}"
}
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.0/angular.js"></script>
<body ng-app="test">
<foo>
<bar></bar>
</foo>
</body>
From my experience, I recommend using the ControllerAs syntax.
I have two charts with different ids (#chart1 and #chart2). I've created a button, so I can change a chart's type (e.g. from column to line):
<button ng-click="updateChart(name, 'line')">Line</button>
This button calls the updateChart function:
$scope.updateChart = function(id, type) {
var chart = $('#' + id).highcharts();
chart.series[0].update({
type: type
});
};
As I need to call the button for every chart, I've created a directive passing a name value to the scope:
.directive('changeChart', function() {
return {
restrict: 'E',
scope: {
name: '#'
},
templateUrl: "change-chart.html"
};
})
In my HTML I call the directive passing the chart id: <change-chart name="chart1"></change-chart>
However, the button isn't working. It only works if I remove the directive scope and set the id manually. Any ideas on how to solve this?
Here's a Plunker: http://plnkr.co/edit/5wmLldmXpoq9wiMACbNS?p=preview
That is because when you define a scope object in your directive, it creates an isolate scope for that directive. That means, the scope within your directive cannot access the scope properties defined outside. You have your controller defined on the outer scope where you attach the function updateChart. So, your isolate scope directive is not aware of this method.
To fix this, you can define a controller on your directive itself. And in that controller, define the method updateChart
.directive('changeChart', function() {
return {
restrict: 'E',
scope: {
name: '#'
},
controller: function ($scope) {
$scope.updateChart = function(id, type) {
var chart = $('#' + id).highcharts();
chart.series[0].update({
type: type
});
};
},
templateUrl: "change-chart.html"
};
})
I have a directive i'm using to do the same search filtering across multiple pages. So the directive will be using a service and get pretty hefty with code. Because of that I want to link to a controller instead of have the controller inside the directive like this:
.directive('searchDirective', function($rootScope) {
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search'
};
});
I also want access to parent scope data inside the template, so I don't want to use a isolated scope.
Anyway here's what i'm not sure how to do. My directive looks like this:
<search-directive filter="foo"/>
How do I pass in the value in the filter attribute so that I can access it in my controller using $scope.filter or this.filter?
If I were using an isolated scope it'd be simple. If i had the controller in the same page I could use $attrs. But since i'm using a controller from another spot and don't want an isolated scope i'm not sure how to get the attrs values into the controller.
Any suggestions?
What about using the link function and passing the value to the scope?
return {
restrict: 'E',
templateUrl:'searchtemplate.html',
controller: 'searchCtrl',
controllerAs: 'search',
link: function (scope, element, attr) {
scope.filter = attr.filter;
}
};
searchDirective.js
angular
.module('searchDirective', []).controller('SearchCtrl', SearchCtrl)
.directive('SearchDirective', directive);
function directive () {
var directive = {
templateUrl:'searchtemplate.html',
restrict: "E",
replace: true,
bindToController: true,
controller: 'searchCtrl as search',
link: link,
scope: { filter:'=' } // <-- like so here
};
return directive;
function link(scope, element, attrs) {}
}
SearchCtrl.$inject = [
'$scope',
'$filter'];
function SearchCtrl(
$scope,
$filter) {
/** Init SearchCtrl scope */
/** ----------------------------------------------------------------- */
var vs = $scope;
// ....
Also I highly recommend checking out this AngularJS style guide, how you are writing your directive above is how I use to do it too. John Papa shows some way better ways: https://github.com/johnpapa/angular-styleguide
Directives:
https://github.com/johnpapa/angular-styleguide#directives
Controllers:
https://github.com/johnpapa/angular-styleguide#controllers
Flip the values of bindToController and scope around.
{
....
scope: true,
bindToController: { filter:'=' }
...
}
I have just hit the same issue over the weekend, and made a simple complete example here: bindToController Not Working? Here’s the right way to use it! (Angular 1.4+)
I have the following directive:
app.directive("actTemplate", function() {
return {
restrict: 'A',
templateUrl: "/views/myTemplate.html"
};
});
how can i pass additional parameter to myTemplate so:
<div>
{{aditionalParam}}
...
</div>
takes the value?
Define
app.directive("actTemplate", function() {
return {
restrict: 'A',
templateUrl: "/views/myTemplate.html"
scope: {
foo: '=boo'
}
};
});
Template
<div>
{{foo}}
</div>
Use
<actTemplate boo="lalala" />
You have to specify that your directive should create it's inner scope. Scope variable could then be shared in single or double binding way (see directive scope binding doc)
app.directive("actTemplate", function() {
return {
restrict: 'A',
scope: {
additionalParam: '='
},
template: "<div>{{additionalParam}}</div>"
};
});
Then "call" your directive with this dashed syntaxe:
<div act-template additional-param="foobar">
You can have one-way binding (controller -> directive) like in this jsfiddle example.
Or two way data binding (controller <-> directive) , like in this one.