Here's a short fiddle:
http://jsfiddle.net/aSg9D/
Basically, neither <div data-foo-{{letterA}}></div> nor <div data-ng:model="foo-{{letterB}}"></div> are interpolated.
I'm looking for a way to dynamically load one of several inline templates.
Pardon me if this has already been asked before, but I searched and couldn't find it.
I believe Radim Köhler has the correct answer. Just before it was posted, I hacked together something to load directives from another directive like this:
angular.module('myApp', []).directive('loadTmpl', function($compile) {
return {
restrict: 'A',
replace: true,
link: function($scope, $element, $attr) {
$element.html("<div data-card-"+$attr.loadTmpl+"></div>");
$compile($element.contents())($scope);
}
};
});
And:
<div data-load-tmpl="{{directiveName}}"></div>
I think that's the minimalist approach, but there's probably something wrong with it, so just look at the answer below.
Let's adjust it this way (the udpated fiddle). The view:
<div my-selector name="letterA"></div>
<div my-selector name="letterB"></div>
the controller:
function myCtrl($scope) {
$scope.letterA = 'bar';
$scope.letterB = 'baz';
}
And here is new directive mySelector, containing the selector
.directive('mySelector',
[ '$templateCache','$compile',
function($templateCache , $compile) {
return {
scope: {
name: '='
},
replace: true,
template: '',
link: function (scope, elm, attrs) {
scope.buildView = function (name) {
var tmpl = $templateCache.get("dir-foo-" + name);
var view = $compile(tmpl)(scope);
elm.append(view);
}
},
controller: ['$scope', function (scope) {
scope.$watch('name', function (name) {
scope.buildView(name);
});
}],
};
}])
.run(['$templateCache', function ($templateCache) {
$templateCache.put("dir-foo-bar", '<div data-foo-bar></div>');
$templateCache.put("dir-foo-baz", '<div data-foo-baz></div>');
}])
In case you like it, all credits goes to Render a directive inside another directive (within repeater template) and AngularJS - Directive template dynamic, if you don't, blame me.
Related
I am learning how to create custom directives.
My service looks like that:
myApp.service('myService',function(){
this.myFunction=function(myParam){
// do something
}
});
Here is my directive:
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="myService.myFunction(param)">Do action</button>',
}
});
In HTML, when I use <my-directive my-param="something"></my-directive> it properly renders as a button. However when I click it, myService.myFunction, doesn't get executed.
I suppose I am doing something wrong. Can someone give me a direction?
I guess this has something to do with the directive's scope.
The service wont be available directly inside the template. You'll have to use a function attached to the directive's scope and call the service function from within this function.
myApp.directive('myDirective',function(myService){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="callService(param)">Do action</button>',
link: function(scope, element, attrs) {
scope.callService = function() {
myService.myFunction();
}
}
}
});
It doesn't work because in your example a directive doesn't actually know what is myService. You have to explicitly inject it e.g.:
myApp.directive('myDirective', ['myService', function(myService){ ... }]);
See also this question or this question.
You should use a controller to do all DOM-modifications.
See this plunkr: https://plnkr.co/edit/HbfD1EzS0av5BG6NgtIv?p=preview
.directive('myFirstDirective', [function() {
return {
'restrict': 'E',
'controller': 'MyFirstController',
'controllerAs': 'myFirstCtrl',
'template': '<h1>First directive</h1><input type="text" ng-model="myFirstCtrl.value">'
};
}
You can inject the service in the controller and then call that function inside your template:
Inject myService into controller:
myApp.controller("ctrl", function($scope, myService) {
$scope.doService = function(myParam) {
return myService.myFunction(myParam);
};
});
Call doService method of the controller inside your template:
myApp.directive('myDirective',function(){
return {
restrict: 'E',
scope: {
param: '=myParam',
},
template: '<button ng-click="doService(param)">Do action</button>',
}
});
My directive has an attribute id-model that is used to pass an array to the directive. The directives controller should execute a function using that array as an argument. I can access the attribute in my controller and log the array but my function never runs and I'm not sure why.
If I don't use the attribute and change placesFact.getDetails(idModel) to placesFact.getDetails($scope.model) everything works fine but I want there to be more flexibility with the directive.
*Update:
To clarify on the placesFact.getDetails() method, It looks up google place info with a place id and returns the results as a promise. It has been tested and works fine.
Directive
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: '#',
link: function(scope, element, attrs) {
scope.tempUrl = attrs.tempUrl;
},
controller: function($scope, $attrs, placesFact) {
var idModel = $attrs.idModel;
$attrs.$observe('idModel', function(value) {
idModel = value;
console.log(idModel); // This logs the array
placesFact.getDetails(idModel).then(function(results) {
console.log(results); // This logs nothing
$scope.places = results;
});
});
},
template: '<div ng-include="tempUrl"></div>'
}
})
Controller
angular.module('myApp').controller('PlacesCtrl', function($scope) {
$scope.model = [
'ChIJR4dOl_hYwokRApSCaQiBidk',
'ChIJv-Ghof5YwokRMtWLEV12hJI',
'ChIJjyX2GqRZwokRT-gdcGoPuSI',
'ChIJqSurReFYwokRec7JFACToas',
'ChIJn8dCo-NYwokRC_4nRUQWbNE',
'ChIJszmN0-JYwokRk-XCDbO6X_Y',
'ChIJt4TrE_1YwokRVedrKxaqYoo',
'ChIJiW0WvwJZwokRIWyzCvo3o5k',
'ChIJsS1xLQJZwokRGfXJPMwXA1A',
'ChIJI5xCX6NZwokR3jdSQwsw2DI',
'ChIJuVE5aLtZwokR-K75OxUEtzI',
'ChIJ7R4tgLtZwokRM8thlhlzE2o',
'ChIJxbWTG_pYwokRPgtFVKi-Cuc',
'ChIJKZVnwFVYwokRgDw_sxw3NCo',
'ChIJw_JUgvhYwokR91EMxVDhB8M',
'ChIJZ3oXOVZYwokRNnAXaDRKAzg',
'ChIJP9idxlZYwokRLH-I1mNfzYQ',
'ChIJndGJ5FNYwokRricJvhT0t1s',
'ChIJhZ0Sn1ZYwokRRA1MZJZHrHA',
'ChIJhTdv51NYwokR7V105uVzf8g',
'ChIJvfVwDFJYwokRtWobbwOMEVM',
'ChIJpY9Tg01YwokRCr_aQpDrqgk',
'ChIJ-fRuLFdYwokR0KKQ6Av_WhQ',
'ChIJh-DIsE1YwokRhuFrdM1ge5E'
];
});
View
<g-places temp-url="pages/places/multiTemp.html" id-model={{model}}></g-places>
As estus mentioned in the comments, my scope: '#' syntax was wrong and was the root of the problem. This is my first directive so a little trial and error and a lot of reading lead me to figure out what to do though.
Here is how the directive looks now that it is working.
.directive('gPlaces', function() {
return {
restrict: 'E',
scope: {
idModel: '=',
tempUrl: '#'
},
controller: function($scope, placesFact) {
placesFact.getDetails($scope.idModel).then(function(results) {
$scope.places = results;
});
},
template: '<div ng-include="tempUrl"></div>'
}
})
When looking for information regarding Angular directives and passing behavior to directives, I get ended up being pointed in the direction of method binding on an isolate scope, i.e.
scope: {
something: '&'
}
The documentation for this functionality is a bit confusing, and I don't think it'll end up doing what I want.
I ended up coming up with this snippet (simplified for brevity), that works by passing a scope function in HomeCtrl, and the directive does it's work and calls the function. (Just incase it matters, the real code passes back a promise from the directive).
angular.module('app', []);
angular.module('app')
.directive('passingFunction',
function() {
var changeFn,
bump = function() {
console.log('bump() called');
internalValue++;
(changeFn || Function.prototype)(internalValue);
},
internalValue = 42;
return {
template: '<button ng-click="bump()">Click me!</button>',
scope: {
onChange: '<'
},
link: function(scope, element, attrs) {
if (angular.isFunction(scope.onChange)) {
changeFn = scope.onChange;
}
scope.bump = bump;
}
};
})
.controller('HomeCtrl',
function($scope) {
$scope.receive = function(value) {
console.log('receive() called');
$scope.receivedData = value;
};
});
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.4/angular.min.js"></script>
<div ng-app="app" ng-controller="HomeCtrl">
<passing-function on-change="receive"></passing-function>
<p>Data from directive: {{receivedData}}</p>
</div>
Is this a proper "Angular" way of achieving this? This seems to work.
What you need is to pass the function to the directive. I'll make a very small example.
On controller:
$scope.thisFn = thisFn(data) { console.log(data); };
In html:
<my-directive passed-fn="thisFn()"></my-directive>
On directive:
.directive('myDirective', [
() => {
return {
restrict: 'E',
scope: {
passFn: '&'
},
template: '<div id="myDiv" ng-click="passFn(data)"></div>',
link: (scope) => {
scope.data = "test";
}
}
}]);
Fixed the issue, here is the final fiddle that shows it working:
http://jsfiddle.net/mbaranski/tfLeexdc/
I have a directive:
var StepFormDirective = function ($timeout, $sce, dataFactory, $rootScope) {
return {
replace: false,
restrict: 'AE',
scope: {
context: "=",
title: "="
},
template: '<h3>{{title}}</h3><form id="actionForm" class="step-form"></form><button ng-click="alert()" type="button">Save</button>',
link: function (scope, elem, attrs) {
}
}
}
How do I make the alert() do something from the controller?
Here is a fiddle:
http://jsfiddle.net/mbaranski/tfLeexdc/
Angular can be twitchy, so I've built a whole new fiddle to demonstrate all of the "glue-up" pieces you need to make this work.
First, you weren't passing the properties through to the directive, so I've made that adjustment:
// You have to pass the function in as an attribute
<hello-directive list="osList" func="myFunc()"></hello-directive>
Second, you were using onclick instead of ng-click in your template, which was part of the problem, so I made that switch:
// You need to use "ng-click" instead of "onclick"
template: '<h3>{{list}}</h3><button ng-click="func()" type="button">Button</button>',
And lastly, you need to bind the function in the scope of the directive, and then call it by the bound name:
scope: {
list: "=",
// Bind the function as a function to the attribute from the directive
func: "&"
},
Here's a Working Fiddle
All of this glued up together looks like this:
HTML
<div ng-controller="MyCtrl">
Hello, {{name}}!
<hello-directive list="osList" func="myFunc()"></hello-directive>
</div>
Javascript
var myApp = angular.module('myApp', []);
function MyCtrl($scope) {
$scope.name = 'Angular Directive';
$scope.osList = "Original value";
$scope.stuffFromController = {};
$scope.myFunc = function(){ alert("Function in controller");};
};
var HelloDirective = function() {
return {
scope: {
list: "=",
func: "&"
}, // use a new isolated scope
restrict: 'AE',
replace: false,
template: '<h3>{{list}}</h3><button ng-click="func()" type="button">Button</button>',
link: function(scope, elem, attrs) {
}
};
};
myApp.directive("helloDirective", HelloDirective);
If you'd like to execute a function defined somewhere else, make sure you pass it in by the scope directive attribute.
Here you can do:
scope: {
context: '=',
title: '=',
alert='&' // '&' is for functions
}
In the place where you using the directive, you'll pass the "expression" of the function (meaning not just the function, but the actual invocation of the function you want to happen when the click occurs.
<step-form-directive alert="alert()" title=".." context=".."></step-form-directive>
I want to dynamically specify a controller based on a config that we load. Something like this:
<div ng-controller="{{config.controllerNameString}}>
...
</div>
How do I do this in angular? I thought this would be very easy, but I can seem to find a way of doing this.
What you want to do is have another directive run before anything else is called, get the controller name from some model remove the new directive and add the ng-controller directive, then re-compile the element.
That looks like this:
global.directive('dynamicCtrl', ['$compile', '$parse',function($compile, $parse) {
return {
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem) {
var name = $parse(elem.attr('dynamic-ctrl'))(scope);
elem.removeAttr('dynamic-ctrl');
elem.attr('ng-controller', name);
$compile(elem)(scope);
}
};
}]);
Then you could use it in your template, like so:
<div dynamic-ctrl="'blankCtrl'">{{tyler}}</div>
with a controller like this:
global.controller('blankCtrl',['$scope',function(tyler){
tyler.tyler = 'tyler';
tyler.tyler = 'chameleon';
}]);
There's probably a way of interpolating the value ($interpolate) of the dynamic-ctrl instead of parsing it ($parse), but I couldn't get it to work for some reason.
I'm using it in ng-repeat, so this is improved code for loops and sub objects:
Template:
<div class="col-xs6 col-sm-5 col-md-4 col-lg-3" ng-repeat="box in boxes">
<div ng-include src="'/assets/js/view/box_campaign.html'" ng-dynamic-controller="box.type"></div>
</div>
Directive:
mainApp.directive('ngDynamicController', ['$compile', '$parse',function($compile, $parse) {
return {
scope: {
name: '=ngDynamicController'
},
restrict: 'A',
terminal: true,
priority: 100000,
link: function(scope, elem, attrs) {
elem.attr('ng-controller', scope.name);
elem.removeAttr('ng-dynamic-controller');
$compile(elem)(scope);
}
};
}]);
Personally the 2 current solutions here didn't work for me, as the name of the controller would not be known when first compiling the element but later on during another digest cycle. Therefore I ended up using:
myapp.directive('dynamicController', ['$controller', function($controller) {
return {
restrict: 'A',
scope: true,
link: function(scope, elem, attrs) {
attrs.$observe('dynamicController', function(name) {
if (name) {
elem.data('$Controller', $controller(name, {
$scope: scope,
$element: elem,
$attrs: attrs
}));
}
});
}
};
}]);