Bind model to function call return value - javascript

I am working on an angular project and I use a directive to create an isolated scope. The directive looks like this:
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '=quiz'
},
link: function (scope, attr, element) {
scope.$watch(function () {
return scope.quiz;
},
function (oldVal, newVal) {
scope.currentQuestion = scope.quiz;
});
}
};
});
For I do not want to bind to a property (or field) in my Controller, I created a function and call the directive this way:
<question quiz="quiz.getCurrentQuestion()">... (transcluding stuff)</question>
Please note that quiz is my Controller using the as-Syntax.
The way I process the directive is working, but I don't like to create a two-way-binding ( to an R-value?).
Now I tried to just pass the function using &-binding but this just turns out odd results in the link-function and breaks everything.
Can I use the function-binding using & and somehow call the function (in my template or in the link-function) to get the result I need to make it work like two-way-binding?
Thank you for your help.
EDIT
The return value of the getCurrentQuestion-function is an object which looks like
{
questionNumber: 1,
answers: [],
getQuestionText() : function(...),
...
}
So nothing to special, I hope...
EDIT 2
When I use
...
scope: {
quiz: '&quiz'
}
then in the $watch-function I get
function(locals) { return parentGet(scope, locals); } for scope.quiz
And if I call the function like scope.quiz() I get undefined as result.

Couldn't find any way to watch a function in scope binding. However, there are other solutions. If you want single way binding you can use '#', but that means that you would have to parse the JSON in the watch ( working example):
var directive = module.directive('question', function () {
return {
restrict: 'E',
templateUrl: 'question.html',
transclude: true,
scope: {
quiz: '#'
},
link: function (scope, attr, element) {
scope.$watch('quiz', function (newVal, oldVal) {
scope.currentQuestion = angular.fromJson(newVal);
});
}
};
});
It works, but if you have a high rate of updates, the overhead can be annoying. What I would do, is use a service that holds all the questions, and both controller and directive can talk to. When the current question is changed, the controller should pass to the directive only the id of the new question (using simple # bind), and the directive would query the service for the question.

Related

Call controller function from nested directive in Angularjs

I have a set of directives that create a nested list with arbitrary depth. I am looking to use ng-click to send a selected <li>'s object back to the controller. The current attempt looks as so:
angular.module('crmProductgallery').directive('categorytree', function() {
return {
template: '<ul><categorytree-node ng-repeat="item in items" getCategories="getCategories"></categorytree-node></ul>',
restrict: 'E',
replace: true,
// controller: 'CrmProductgallerygallery',
scope: {
items: '=items',
getCategories: '&',
}
};
});
angular.module('crmProductgallery').directive('categorytreeNode', function($compile) {
return {
restrict: 'E',
template: '<li ng-click="getCategories()" getCategories="getCategories">{{item.title}}</li>',
// replace: true,
// controller: 'CrmProductgallerygallery',
// scope: {
// getCategories: '&',
// items: "="
// },
link: function(scope, elm, attrs) {
scope.$watch('items', function() {
$(elm).on('click', function(e) {
scope.getCategories(scope.item);
console.log(scope.item);
// https://stackoverflow.com/questions/15390393/two-nested-click-events-with-angularjs
e.stopPropagation();
});
if (scope.item.children) {
var children = $compile('<categorytree items="item.children" getCategories="getCategories"></categorytree>')(scope);
elm.append(children);
}
});
}
};
});
The code produces the following unordered list:
Currently, when I click on a <li>, scope.item is logged inside my scope.$watch() function, but the controller function getCategories(item) is not called.
If I uncomment out the scope declaration...
...in the categorytreeNode directive, I will get Cannot read properties of undefined (reading 'children). The list does not get created. scope.item no longer exists when trying that.
If I uncomment controller: 'CrmProductgallerygallery', the ng-click works and my controller function is called, but the list elements get duplicated over and over again. Here is what I mean:
Controller Function works on click now:
But now my list is duplicating:
You will also notice I placed getCategories="getCategories" everywhere I could think of trying. None of it works though. I think it is because the <ul> element is getting assigned ng-isolate-scope:
The controller function simply is this:
$scope.getCategories = function(item) {
console.log("getCategories");
console.log(item);
};
Question:
How can I get ng-click on the <li> elements to call my getCategories() controller function

Update UI based on change of a directive attribute in AngularJs

I am struggling with data binding in AngularJs.
I have the following piece of markup in .html file that includes the custom directive:
<my-directive ng-repeat="i in object" attr-1="{{i.some_variable}}"></my-directive>
Note: 'some-variable' is being updated every 10 seconds(based on the associate collection and passed to template through controller).
The directive's code includes:
myApp.directive('myDirective', function () {
scope: {
'attr-1': '=attr1'
which throws this exception because of the brackets in attr-1(see html code above).
It works though if I use read-only access(note at sign below):
myApp.directive('myDirective', function () {
scope: {
'attr-1': '#attr1'
I use scope.attr-1 in directive's HTML to show its value.
The problem is that with read-only access UI is not reflecting the change in attribute change.
I've found solution with $parse or $eval(couldn't make them work tho). Is there a better one there?
You'll need only two-way binding and I think $parse or $eval is not needed.
Please have a look at the demo below or in this fiddle.
It uses $interval to simulate your updating but the update can also come from other sources e.g. web socket or ajax request.
I'm using controllerAs and bindToController syntax (AngularJs version 1.4 or newer required) but the same is also possible with just an isolated scope. See guide in angular docs.
The $watch in the controller of the directive is only to show how the directive can detect that the data have changed.
angular.module('demoApp', [])
.controller('MainController', MainController)
.directive('myDirective', myDirective);
function MainController($interval) {
var self = this,
refreshTime = 1000; //interval time in ms
activate();
function activate() {
this.data = 0;
$interval(updateView, refreshTime);
}
function updateView() {
self.data = Math.round(Math.random()*100, 0);
}
}
function myDirective() {
return {
restrict: 'E',
scope: {
},
bindToController: {
data: '='
},
template: '<div><p>directive data: {{directiveCtrl.data}}</p></div>',
controller: function($scope) {
$scope.$watch('directiveCtrl.data', function(newValue) {
console.log('data changed', newValue);
});
},
controllerAs: 'directiveCtrl'
}
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.js"></script>
<div ng-app="demoApp" ng-controller="MainController as ctrl">
model value in ctrl. {{ctrl.data}}
<my-directive data="ctrl.data"></my-directive>
</div>
I've come to the following solution(in case somebody runs into the the same problem):
// Directive's code
myApp.directive('myDir', function () { return {
restrict: 'E',
templateUrl: function () {
return 'my-dir.html';
},
scope: {
'id': '#arId',
'x': '#arX',
'y': '#arY',
//....
},
link: function ($scope, element, attrs) {
// *** SOLUTION ***
attrs.$observe('arId', function (id) {
$scope.id = id;
});
//...
}
Update: somebody sent me this answer, they have the same problem and came up with a very similar if not exact same solution:
Using a directive inside an ng-repeat, and a mysterious power of scope '#'
It is useful to read because they explain what's the idea behind it.

Pass directive attribute into linked controller?

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+)

Angular: Call directive controller function from separate controller

Let's say I have an app called myApp. There is a directive for a menu component. This directive has a controller defined on it. Now, from another controller that loads the view, I want to call methods of that menu directive's controller.
What does the code look like to do that? How can the other controller call methods on the menu directive's controller?
I think the issue is that you're trying to go controller -> directive when you should be binding a function to some delegating call that goes controller <- directive.
For an example of this, you can check out angular-ui/bootstrap's tabs directive onSelect and onDeselect:
.directive('tab', ['$parse', function($parse) {
return {
require: '^tabset',
restrict: 'EA',
replace: true,
templateUrl: 'template/tabs/tab.html',
transclude: true,
scope: {
active: '=?',
heading: '#',
onSelect: '&select', //This callback is called in contentHeadingTransclude
//once it inserts the tab's content into the dom
onDeselect: '&deselect'
},
// etc.
Using the & expression allows you to bind to a function as a callback.
An example of this usage would be a scope-bound function in your target controller:
$scope.selectedTab = function(){ alert('woohoo'); };
Which is 'bound' to the directive's function in the view:
<tab select="selectedTab()">
The tabs directive also has a good example of how to communicate between two different directives controllers. See tabset controller's ctrl.select. A tab directive requires a parent tabSet controller with the require:
.directive('tab', ['$parse', function($parse) {
return {
require: '^tabset',
The tab directive can access the tabset controller's select function in the compile function:
compile: function(elm, attrs, transclude) {
return function postLink(scope, elm, attrs, tabsetCtrl) {
scope.$watch('active', function(active) {
if (active) {
tabsetCtrl.select(scope);
}
});
scope.disabled = false;
if ( attrs.disabled ) {
scope.$parent.$watch($parse(attrs.disabled), function(value) {
scope.disabled = !! value;
});
}
scope.select = function() {
if ( !scope.disabled ) {
scope.active = true;
}
};
tabsetCtrl.addTab(scope);
scope.$on('$destroy', function() {
tabsetCtrl.removeTab(scope);
});
//We need to transclude later, once the content container is ready.
//when this link happens, we're inside a tab heading.
scope.$transcludeFn = transclude;
};
}
So there you have how to execute code from some non-directive controller as a result of some action happening in your directive and how to execute some inter-directive functions as a result of directive require constraints.
edit: For anyone interested, here's the documentation for a directive's expressions: http://docs.angularjs.org/api/ng/service/$compile#comprehensive-directive-api

In a directive, handle calls to a user defined method name

Is there a way to allow the user to give a method name to a directive, let the directive create that method on the scope, and then handle calls to that method?
So, first I let the user define a method name HELLO, and then I let the user call HELLO from elsewhere (still in the same scope)
<div ng-controller="AppController">
<div mydirective="" mydirective-data="MyJson" mydirective-fx="HELLO" />
<button ng-click="HELLO()">Click me</button>
</div>
Internally, the directive should see the HELLO and map it to its own method. In the directive, I am looking at the method name being passed in and assigning it
app.directive('mydirective', function() {
return {
restrict: 'A',
scope: {
data: '=mydirectiveData',
fx: '=mydirectiveFx'
},
link: function(scope, element, attrs) {
scope.fx = function () { console.log(scope.data); } ;
}
}
}
);
as you can see, I am assigning scope.fx, which should be HELLO, to a function which should read scope.data, defined in the controller.
Attempting this does not do anything nor does it throw an error. It makes me wonder if I am doing this the wrong way.
For clarity, I have created a plunker. Remember to open the console.
Use # instead of = then scope[scope.fx] to create the property:
app.directive('mydirective', function() {
return {
restrict: 'A',
scope: {
data: '=mydirectiveData',
fx: '#mydirectiveFx'
},
link: function(scope, element, attrs) {
scope[scope.fx] = function () { console.log(scope.data); };
}
}
}
);
http://plnkr.co/edit/a2c14O?p=preview

Categories

Resources