Can a child directive require a parent without knowing exactly which directive that parent is, just that it "implements an interface"?
For example:
<parentImplX>
<child></child>
</parentImplX>
In the above example I want the controller injected into child to be ParentImplXCtrl. But If I do:
<parentImplY>
<child></child>
</parentImplY>
I want the controller to be ParentImplYCtrl.
directives.directive("parentImplX", function () {
return {
scope: {},
restrict: "E",
controller: ParentImplXCtrl
}
});
directives.directive("parentImplY", function () {
return {
scope: {},
restrict: "E",
controller: ParentImplYCtrl
}
});
directives.directive("child", function () {
return {
scope: {},
restrict: "E",
require: "?^^parentInterface",
link: function ($scope, $element, attributes, parent /* type ParentInterface */) {
parent.method();
}
}
});
I've found that angular 'require' does not support this. However, AngularJS stores the controllers as well as the $scopes of $elements in the $element.data() construct. So it was very simple to write your own 'interface require'. You need to traverse $element.parent().data() and make sure there is an identifier to look for. In my case isFocusNode. Note: `FocusNode can have many implementations. It is the whole point.
function findFocusNodeParent(_element) {
var data = _element.data();
for (var key in data) {
var angularObject = data[key];
if (angularObject.isFocusNode && angularObject.isFocusNode()) {
return angularObject;
}
}
var _parentElement = _element.parent();
if (_parentElement.length > 0) {
return findFocusNodeParent(_parentElement);
} else {
// No parent FocusNode found. Must be root
return null;
}
}
var parentFocusNodeController = findFocusNodeParent($element);
Related
I have two directives, calling 2nd directive from 1st directive.
This is my 1st directive
var initializeWidget = function ($compile, $timeout, $rootScope) {
return {
restrict: 'EA',
scope: {
maxImages: '#',
},
link: function (scope, element, attrs) {
if (!scope.cloudinaryFolder) {
throw 'folder value is missing in image uploader directive';
}
if (!scope.cloudinaryTags) {
throw 'tags value is missing in image uploader directive';
}
//1
attrs.$observe('maxImages', function (newMaxImages) {
console.log('varun==' + newMaxImages);
$timeout(function () {
angular.element(document.body).append($compile('<div class="sp-upload-widget" sp-upload-widget up-max-images="' + scope.maxImages + '"></div>')(scope));
scope.$apply();
}, 10);
});
}
};
};
I am calling my 2nd directive usixng angular.element used in above code.
Below is my 2nd directive:
var spUploadWidget = function ($q, Cloudinary, ENV) {
var templateUrl;
if ('dev' === ENV.name) {
templateUrl = '/seller/modules/uploadWidget/views/upload.html';
}
else if ('prod' === ENV.name) {
templateUrl = './views/upload.html';
}
return {
restrict: 'AE',
scope: {},
bindToController: {
maxImages: '=?upMaxImages',
},
replace: false,
controller: 'uploadWidgetController',
controllerAs: 'up',
templateUrl: templateUrl,
};
};
now in my controller when I am checking value of maxImages then it is giving the updated value but when I am using this variable to call API then it is holding the older value. Here is my controller
console.log('up===' + self.maxImages);
self.openUploader = function () {
self.closeModal();
ABC.UploaderInit( self.maxImages);
};
So when I change the value of maxImages in my directive
<div initialize-widget max-images="maxImages"></div>
It should give the updated value to my ABC.UploaderInit function
Found a solution for my problem,
I was getting this problem because I was calling 2nd directive whenever attribute of 1st directive was changing so I was creating multiple instances of my directive.
So now to handle this I am destroying the older instance of 2nd directive before I call the 2nd directive.
$rootScope.$on('destroySpUploadWidget', function (event, args) {
if (args.modalId === ctrl.modalId) {
scope.$destroy();
element.remove();
}
I am creating a directive where two other child directives use it is functionalities but I need a help to know how to make the scope shared between the parent and the child directive
Parent directive
MetronicApp.directive('atmSearchComponent', function () {
function AtmSearchComponentController(scope) {
// initialize scope
}
AtmSearchComponentController.prototype.search = function () {
scope.mainGridOptions.dataSource.page(1);
reset();
}
AtmSearchComponentController.prototype.clearSearch = function () {
scope.data.basicSearch = {};
scope.data.advancedSearch = {};
}
AtmSearchComponentController.prototype.keyPressSearch = function (keyEvent) {
if (keyEvent.which === 13) {
keyEvent.preventDefault()
scope.search();
}
};
return {
restrict: 'E',
controller: ['$scope', AtmSearchComponentController],
scope: {}
};
});
Child directive
MetronicApp.directive('atmsQuerySelector', function ($log, $parse, $timeout, $http, $uibModal) {
return {
restrict: "E",
replace: false,
scope: {},
require: ['^atmSearchComponent', 'ngModel'],
templateUrl: "/apps/framework/templates/atmsSelector.view",
link: function (scope, element, attrs, controllersArray) {
scope.search = controllersArray[0].search;
clearSearch = controllersArray[0].clearSearch;
scope.keyPressSearch = controllersArray[0].keyPressSearch;
},
}
});
but when I call the search method I get the following error
ReferenceError: scope is not defined
at n.AtmSearchComponentController.search (directives.js:8)
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());
}
I have several very similar directives which share some code. I'm looking for a way to have those directives inherit that shared code.
The approach I'm considering is having each directive's template include another directive with the shared code. Is there any way then to access that template's directive's controller?
app.directive('d1', function($compile) {
return {
restrict: 'E',
require: 'sharedCtrl', // Is anything like this possible?
template: '<shared></shared>',
link:function(scope,element,attr, sharedCtrl) {
var res = sharedCtrl.getResult();
}
}
});
app.directive('shared', function($compile) {
return {
restrict: 'E',
controller: function($scope, $element) {
var resultObject = sharedCode();
this.getResult = function() { return resultObject; };
}
link:function(scope,element,attr, sharedCtrl) {
}
}
});
Or alternatively, is there a better way to achieve the same thing?
Edit: The shared code manipulates the DOM (it injects a leaflet map, and I want to return the map js object), so I think it is best to keep DOM manipulation in a directive.
This is exactly what services are for.
Turn your shared directive into a service and inject it into the first one.
app.directive('d1', function($compile, shared) {
return {
restrict: 'E',
template: '<shared></shared>',
link:function(scope,element,attr) {
var res = shared.getResult();
}
}
});
app.factory('shared', function($compile) {
var resultObject = sharedCode();
return {
getResult: function() { return resultObject; };
}
});
I'm currently adding a function in the directive module that does the shared DOM manipulation.
function sharedFoo(element) {
// DOM manipulation on element
return result;
}
app.directive('d1', function($compile) {
return {
restrict: 'E',
link:function(scope,element,attr, sharedCtrl) {
var res = sharedFoo();
}
}
});
Works fine, but I'm not sure how much it follows or breaks angular conventions.
My goal is to output a value (from a service) through a element directive so that the html will look like this <msg msg="alertMsg"></msg> and out pops a value from the service.
Here is my code thus far:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: {//something here to pass MsgService to template },
template: 'Message:{{MsgService.getAlertMsg()}}'
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
HTML would parse/compile to...
<msg msg="alertMsg">Message: default</msg>
What other code do I need?
If a service wont work directly, Should I access it through a controller?
app.directive("msg", function() {
return {
restrict: "E",
scope: {
getMsg: '&msg'
},
controller: 'MsgController',
template:'Message:{{getMsg()}}'
};
}]);
app.controller('MsgController', ['MsgService' , function(MsgService){
this.getAlertMsg = function(){
return MsgService.getAlertMsg();
};
}]);
HTML would parse/compile to...
<msg msg="getAlertMsg()">Message: default</msg>
Sorry for any errors in code or function use, I'm fairly new to Angular.
You can use the link function of the directive. This function is called once for every rendered instance of your directive. It receives, among other things, the scope of your directive. You can extend your scope very easily with the result of calling the MsgSevice.getAlertMsg() service method:
var app = angular.module("app", []);
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: true,
template: 'Message:{{msg}}',
link: function (scope, $element, attrs) {
scope.msg = MsgService.getAlertMsg();
}
};
}]);
app.service('MsgService', function() {
this.alertMsg = 'default';
this.getAlertMsg = function(){
return this.alertMsg;
};
this.setAlertMsg = function(string) {
this.alertMsg = string;
};
});
Later on, I presume you will want to just display the alert message from the msg DOM attribute of the msg directive. Achieving this is much more simple, since AngularJS is already prepared for this common use case. The solution involves creating an isolate scope. The isolate scope can be populated with properties from the parent environment. One possibility is to use the value of a DOM attribute from your directive's element using the "#" syntax. In this case you won't even need the entire MsgService service:
app.directive("msg", function () {
return {
restrict: "E",
scope: {
"msg": "#"
},
template: 'Message:{{msg}}'
};
});
Simplest would be to set the service on your scope and use that in your template:
app.directive("msg", ['MsgService', function(MsgService) {
return {
restrict: "E",
scope: { },
template: 'Message:{{MsgService.getAlertMsg()}}',
link: function(scope, element, attrs) {
scope.MsgService = MsgService;
}
};
}]);