Unable to call Angular Directiive Method on Button Click - angularjs

I'm trying to call directive method on button click from the calling controller.
Here is the directive code:
myApp.directive("helloDirective", function() {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
controller: 'helloDirectiveController',
controllerAs: 'model'
}
})
.controller("helloDirectiveController", function() {
var self = this;
self.actions = {
get: function() {
return self.msg;
},
set: function(msgData) {
self.msg = msgData;
}
});
I have call the get and set method from controller..
myApp.controller("indexController", [function() {
var self = this;
self.helloParam ={};
self.get = function() {
//how to call the Directive get method from here
}
}]);
i tried to create a fiddle here
plnkr

The idea
For me, the cleanest solution (so far) for your problem is a solution used by Angular Material developers (I don't know if they were the authors, but I found it there). I've used it once in my project and it worked like a charm.
The idea is to create a global registry for directives' actions. Directives would be stored there by unique ids. We can also create a service dedicated for each directive, just in case we need some external logic.
The solution
1. Components registry
Firstly, we need a components registry. It can be a really simple service:
angular.module('app');
.service('$componentsRegistry', function() {
var self = this;
var components = {};
self.put = function(id, actions) {
components[id] = actions;
}
self.get = function(id) {
return components[id];
}
})
The components registry has methods for storing and getting components by ids. Of course, there might be much more methods and they might be more complicated, but this is just a simple example.
2. Service for our directive
Let's say we have a simple show-message directive, so we can create a $showMessageService:
angular.module('app')
.service('$showMessageService', ['$componentsRegistry', function($componentsRegistry) {
return function(id) {
return $componentsRegistry.get(id);
}
}])
For now, the only task of the service is to return our directive's actions. But it can be extended in the future, of course.
3. Directive's code
The last thing we need is our show-message directive:
angular.module('app')
.directive('showMessage', function($componentsRegistry) {
return {
restrict: 'E',
scope: {
directiveId: '#' // Unique id is passed from the view
},
template: '<div>{{ text }}</div>',
link: function(scope) {
scope.text = 'TEST TEXT';
// Create actions
scope.actions = {
set: function(value) {
scope.text = value;
}
}
// Store actions in the components registry
$componentsRegistry.put(scope.directiveId, scope.actions);
}
}
})
In a link function, we need to simply register our actions in components registry. We pass the unique id from the view so that developer has control over it inside views/controllers.
Example of usage
And now we can finally use the directive in our application. Here is a simple code which shows how to do that:
View
<div ng-controller="testController as test">
<show-message directive-id="testDirective"></show-message>
<button ng-click="test.changeText()">CHANGE</button>
</div>
Controller
angular.module('app')
.controller('testController', function(['$showMessageService', $showMessageService) {
var self = this;
self.changeText = function() {
$showMessageService('testDirective').set('NEW');
}
}])

I've Change The Directive Code, moved Controller code to link and it's working fine.
testApp.directive("helloDirective", function () {
return {
restrict: "E",
template: '<input type="text" data-ng-model="model.msg" />',
scope: {},
bindToController: {
param: "="
},
link: function (scope, element, attrs) {
var self = scope.model;
var assignMethod = function () {
if (self.param == undefined) {
self.param = {};
}
self.param.actions = {
get: function () {
return self.msg;
},
set: function (msgData) {
self.msg = msgData;
}
};
};
assignMethod();
},
controller: function () { },
controllerAs: 'model'
}
});
now i can call the directive get Method from calling controller like,
self.helloParam = {};
self.click = function () {
alert(self.helloParam.actions.get());
}

Related

Is it possible with Angularjs directive to watch vars in dynamic controller

I have a service that's being used in different controllers, and I would like to consolidate them into a directive that can be applied to the different pages. To start, I'm just trying to get a simple example to work. Say I have two controllers, both of which have a common function:
angular.module('myApp')
.controller('CtrlOne', function() {
$scope.watchVar = 0;
$scope.changeVar = function() {
$scope.watchVar = 1;
}
});
angular.module('myApp')
.controller('CtrlTwo', function() {
$scope.watchVar = a;
$scope.changeVar = function() {
$scope.watchVar = b;
}
});
Then, in a directive I'm trying to determine the controller at runtime, so that this variable can be watched regardless of the controller the page has (as long as it uses the 'watchVar' variable):
angular.module('myApp').directive('myDirective', function () {
return {
scope: {
ctrl: '#myDirective',
watchVar: '=',
},
controller: ctrl,
link: function(scope, element, attrs) {
scope.$watch('watchVar', function(newValue, oldValue) {
if (newValue) {
console.log("watchVar changed to: "+newValue);
} else {
console.log("watchVar remained: "+oldValue);
}
}, true);
}
};
});
With the HTML being something like this:
<div p2a-filter-cache='CtrlOne'>
Is this even possible?

Include different angular directives in ng-repeat

I have several angular directives.
I want to include them in an accordion directive with an ng-repeat depending on the type.
I want it to be reusable, so I don't want the accordion to have knowledge of the type of directives it is rendering.
That's why I don't want to use an ng-switch directive inside the accordion.
Here is a very simplified demo. In reality those directives will have their own attributes for angular to compile.
var testApp = angular.module('testApp', []);
(function() {
function Element1() {
return {
template: '<span>hello</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementOne', Element1);
}());
(function() {
function Element2() {
return {
template: '<span>world</span>',
restrict: 'E',
replace: true
}
}
testApp.directive('elementTwo', Element2);
}());
(function() {
function Accordion() {
return {
template: '<ul><li ng-repeat="element in elements"><button>Toggle</button> Here should be the directive</li></ul>',
restrict: 'E',
replace: true,
controller: function($scope) {
$scope.elements = [{
type: 1
}, {
type: 2
}];
}
}
}
testApp.directive('elementAccordion', Accordion);
}());
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<div ng-app="testApp">
<element-accordion></element-accordion>
</div>
Thanks!
I've built something very similar a few weeks ago. I wanted to render a list of directives and select the type of directive based on the type of an element in an array (just like you). I tried to avoid ngSwitch because I've really many directives and want to add new directives without changing the ngSwitch statement every time. Let me show a simplified version of what I did. I hope this will help you to build your own version.
First, we need a global directory of element diretives and a helper function to register new directives. In my case, the helper function even creates the directive, but I'll skip this to keep it simple:
angular.module("app").provider('elementDirectory', function() {
return {
elements: [],
$get: function () {
return {
elements: this.elements
}
}
};
});
function registerElementDirective(type, directiveName) {
angular.module("app").config(['elementDirectoryProvider', function (elementDirectoryProvider) {
elementDirectoryProvider.elements.push({
type : type,
directive : directiveName
});
}]);
}
Now, we can create some directives like this:
angular.module("app").directive('elementA', function() {
return {
template: '<span>ElementA</span>',
}
});
registerElementDirective('A', 'element-a');
The most interesting part is a directive I called elementSwitch. It takes an array of elements and dynamically adds an angular.element for each element. Therefore, it creates prototypes for each element in the elementDirectoy and uses the clone() method on changes. (I think we could skip this part, it was an optimization).
angular.module('app').directive('elementSwitch', elementSwitch);
elementSwitch.$inject = ['$compile', 'elementDirectory'];
function elementSwitch($compile, elementDirectory) {
var prototypes = {};
elementDirectory.elements.forEach(function (e) {
var directiveElem = angular.element('<' + e.directive + '>');
prototypes[e.type] = directiveElem;
});
return {
scope: {
elements: '=elementSwitch'
},
link: function (scope, elem, attr) {
var childScopes = [];
scope.$watchCollection('elements', function (newValue, oldValue) {
childScopes.forEach(function (s) {
s.$destroy();
});
childScopes = [];
elem.empty();
newValue.forEach(function (element, index) {
var childScope = scope.$new();
childScopes.push(childScope);
var directiveElem = prototypes[element].clone();
directiveElem.attr('element', '_elements[' + index + ']');
$compile(directiveElem)(childScope);
elem.append(directiveElem);
});
scope._elements = newValue;
});
}
}
}
Here is a complete example: https://codepen.io/hansmaad/pen/oLvRmj
It doesn't do the same thing you want, but you should get an idea how you could achieve your goal.

Passing an object as attribute to compiled directive on the fly

I have a angular element on the page which needs to communicate with the rest of the non angular page elements.
I am creating directive elements on the fly, and appending it to its target div. I am trying to pass that created directive an object (ajax object), which contains just attributes.
The issue is that I can't figure out how to pass just this ajax object to the directive, as $compile requires a scope. When the http finishes, and because i have to use = in the directive, the directives are being over-ridden.
Please see my plunk: https://plnkr.co/edit/brTWgUWTotI44tECZXlQ ( sorry about the images ). Click the <button> to trigger the directive.
(function() {
'use strict';
var CHANNEL = 'podOverlay';
angular.module('CavernUI', [])
.controller('CavernCtrl', function($scope,getItemService) {
$scope.model = {};
var _pods = $scope.model.pods = {};
function getData(selector) {
$(selector).each(function(i, pod) {
_pods[+pod.dataset.item] = {
$: $(pod)
};
});
Object.keys($scope.model.pods).map(function(key) {
getItemService.getItem(key).success(function(response) {
_pods[key] = angular.extend(_pods[key], response);
$scope.$broadcast(CHANNEL, _pods[key], $scope);
});
})
}
$scope.runPodCheck = function(selector) {
getData(selector);
}
})
.directive('podchecker', function($compile) {
var createOverlay = function(e,data,scope){
scope.data = data;
// can i just pass data rather than scope.data?
// If I pass the scope, then when another $broadcast happens
// the scope updates, wiping out the last scope change.
// Scope here really needs to be a static object that's
// created purely for the hand off. But I don't know if
// that can be done.
angular.element(data.$[0]).empty().append($compile('<overlay data="data"></overlay>')(scope));
}
return {
restrict: 'E',
scope: {
check: '&',
},
templateUrl: 'tpl.html',
link: function(scope,elm,attr){
scope.$on(CHANNEL,createOverlay);
}
};
})
.directive('overlay', function() {
return {
restrict: 'E',
scope: {
o: '=data' // here is the problem.
},
template: '<div class="overlay"><img ng-src="{{o.images.IT[0]}}"/></div>',
link: function(scope, elm, attr) {
}
}
})
.service('getItemService', ['$http', function($http) {
this.getItem = function(itemId) {
return $http({
method: 'GET',
url: 'https://www.aussiebum.com/ajaxproc/item',
params: {
id: itemId,
ajxop: 1
},
});
};
}]);
}());
Edits:
Expected ouput:
I'm not sure this is the best approach, but one way might be to manually create a new scope for each of the overlays.
So changed this:
var createOverlay = function(e,data,scope){
scope.data = data;
angular.element(data.$[0]).empty().append($compile('<overlay data="data"></overlay>')(scope));
}
to this:
var createOverlay = function(e,data,scope){
var overlayScope = scope.$new(false); // use true here for isolate scope, false to inherit from parent
overlayScope.data = data;
angular.element(data.$[0]).empty().append($compile('<overlay data="data"></overlay>')(overlayScope));
}
Updated Plnkr: https://plnkr.co/edit/wBQ1cqVKfSqwqf04SnPP
More info about $new()
Cheers!

Extending Chips from angular-material

i'm trying to create a layer on top of the md-chips called chip-filter
Which has some extra functionality build in;e.g.
a navigation chip (when removed, redirects the user back to a specific url)
add from any place n the app a chip
...
So I got the basics working, hooking in the componentsRegistry, and being able to call it from any place.
But now I'm trying to get the chips from the chipFilterController into the <md-chips>
html:
<chip-filter md-component-id="testId">
<md-chips ng-model="chips">
<md-chip-template>
<strong>{{$chip}}</strong>
<em>(type)</em>
</md-chip-template>
</md-chips>
</chip-filter>
and directive:
function chipFilterDirective($log) {
function postLink(scope, element, attr, sidenavCtrl) {
element.on("$destroy", function() {
sidenavCtrl.destroy();
});
}
return {
restrict: "E",
scope: {},
controller: "chipFilterController",
compile: function(element) {
return postLink;
}
};
}
and controller:
function chipFilterController($scope, $element, $attrs, $mdComponentRegistry, $q, $log) {
var self = this;
$scope.chips = [];
function addChip(input, type) {
var def = $q.defer();
$scope.chips.push({
name: input,
type: type
});
def.resolve();
return def.promise;
}
self.addNavigationChip = function() {
return addChip("stuff");
};
self.addChip = function() {
return addChip("stuff");
};
self.destroy = $mdComponentRegistry.register(self, $attrs.mdComponentId);
}
full codepen:
http://codepen.io/cskiwi/pen/eJryqK?editors=1010
setting the scope to false (or removing it from the object) made it work

How to dynamically inject service into directive in angularjs

My directive is to build out a scaffolded table with a delete button. I have a service set up named Post, so when I call the directive I'm including a servicename property on the element. How do I dynamically inject my service based on my service name attribute I'm including? Or is there a better way to do this?
var lassoDirectives = angular.module('lassoDirectives', []);
lassoDirectives.directive('autoTable', ['parse', function(parse) {
return {
restrict: 'AE',
scope: {
data: '=',
modelname: '='
},
templateUrl: 'http://local.angular.com/webroot/app/templates/directives/auto-table.html',
controller: function($scope) {
console.log(parse);
$scope.deleteItem = function(id) {
console.log(id);
// rocket = modelname.query();
//console.log(rocket);
}
$scope.check = function() {
parse.get("Posts", 123, function(response) {
console.log(response.toString());
});
}
}
};
}]);
I'm not sure that is the best way to go around it. You should probably have a single service that is charge of all the data related to this lassoDirective, so you could just inject it as normal.
But you may be able to use the $injector provider like...
.directive('autoTable', function($injector) {
return {
restrict: 'EA',
scope: {
servicename: '='
},
controller: function(scope) {
var service = $injector.get(scope.servicename);
scope.deleteItem = function(Id) {
del = service.delete({Id: Id});
items = service.query();
};
}
}
});
If you want your service to perform differently depending on the directive clicked, it may be better to just pass your service additional parameters relating to the directive? Not sure what your situation is so ignore this common if N/A.

Resources