I'm trying to create a custom compile function, to make it easier to dynamically add HTML to a page.
The argument htmlStr is the incoming HTML to compile. The argument value is a variable that can be added to the scope. The argument compiledHTMLFunc is a function that will be executed with the compiled object. Here's my code:
function compileHTML (htmlStr, value, compiledHTMLFunc)
{
var $injector = angular.injector (["ng", "angularApp"]);
$injector.invoke (function ($rootScope, $compile)
{
$rootScope.value = value;
var obj = angular.element (htmlStr);
var obj2 = $compile (obj)($rootScope);
if (compiledHTMLFunc != null)
compiledHTMLFunc (obj2);
});
}
Here's how I use the function:
compileHTML ("<button class = \"btn btn-primary\">{{ value }}</button>", "Ok", function (element)
{
$(document.body).append (element);
});
Whenever I try to compile the following HTML, the inline {{ value }} doesn't get compiled. Even if I simply change it to {{ 1+1 }}. Why is this?
Update: I dunno why I didn't create a fiddle earlier, here's an example: http://jsbin.com/vuxazuzu/1/edit
The problem appears to be pretty simple. Since you invoke compiler from outside of angular digest cycle you have to invoke it manually to boost the process, for example by wrapping compiledHTMLFunc into $timeout service call:
function compileHTML (htmlStr, scope, compiledHTMLFunc) {
var $injector = angular.injector(["ng", "angularApp"]);
$injector.invoke(function($rootScope, $compile, $timeout) {
$rootScope = angular.extend($rootScope, scope);
var obj = $compile(htmlStr)($rootScope);
if (compiledHTMLFunc != null) {
$timeout(function() {
compiledHTMLFunc(obj);
});
}
});
}
compileHTML('<button class="btn btn-primary">{{value}}</button>', {value: 'Ok'}, function(element) {
angular.element(document.body).append(element);
});
I also improved your code a little. Note how now compileHTML accepts an object instead of single value. It adds more flexibility, so now you can use multiple values in template.
Demo: http://plnkr.co/edit/IAPhQ9i9aVVBwV9MuAIE?p=preview
And here is your updated demo: http://jsbin.com/vuxazuzu/2/edit
Related
How might one go about passing an object to Angular's (Angular 1.4.8) & ampersand scope binding directive?
I understand from the docs that there is a key-destructuring of sorts that needs named params in the callback function, and the parent scope uses these names as args. This SO answer gives a helpful example of the expected & functionality. I can get this to work when explicitly naming the params on the parent controller function call.
However, I am using the & to execute actions via a factory. The parent controller knows nothing of the params and simply hands the callback params to a dataFactory, which needs varied keys / values based on the action.
Once the promise resolves on the factory, the parent scope updates with the returned data.
As such, I need an object with n number of key / value pairs, rather than named parameters, as it will vary based on each configured action. Is this possible?
The closest I have seen is to inject $parse into the link function, which does not answer my question but is the sort of work-around that I am looking for. This unanswered question sounds exactly like what I need.
Also, I am trying to avoid encoding/decoding JSON, and I would like to avoid broadcast as well if possible. Code stripped down for brevity. Thanks...
Relevant Child Directive Code
function featureAction(){
return {
scope: true,
bindToController: {
actionConfig: "=",
actionName: "=",
callAction: "&"
},
restrict: 'EA',
controllerAs: "vm",
link: updateButtonParams,
controller: FeatureActionController
};
}
Child handler on the DOM
/***** navItem is from an ng-repeat,
which is where the variable configuration params come from *****/
ng-click="vm.takeAction(navItem)"
Relevant Child Controller
function FeatureActionController(modalService){
var vm = this;
vm.takeAction = takeAction;
function _callAction(params){
var obj = params || {};
vm.callAction({params: obj}); // BROKEN HERE --> TRYING
//TO SEND OBJ PARAMS
}
function executeOnUserConfirmation(func, config){
return vm.userConfirmation().result.then(function(response){ func(response, config); }, logDismissal);
}
function generateTasks(resp, params){
params.example_param_1 = vm.add_example_param_to_decorate_here;
_callAction(params);
}
function takeAction(params){
var func = generateTasks;
executeOnUserConfirmation(func, params);
}
Relevent Parent Controller
function callAction(params){
// logs undefined -- works if I switch to naming params as strings
console.log("INCOMING PARAMS FROM CHILD CONTROLLER", params)
executeAction(params);
}
function executeAction(params){
dataService.executeAction(params).then(function(data){
updateRecordsDisplay(data); });
}
I think the example below should give you enough of a start to figure out your question:
<!DOCTYPE html>
<html ng-app="myApp">
<head>
<meta charset="utf-8">
<title>Angular Callback</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<script>
var myApp = angular.module("myApp", []);
myApp.controller('appController', function($scope) {
$scope.var1 = 1;
$scope.handleAction1 = function(params) {
console.log('handleAction1 ------------------------------');
console.log('params', params);
}
$scope.handleAction2 = function(params, val1) {
console.log('handleAction2 ------------------------------');
console.log('params', params);
console.log('val1', val1);
}
});
myApp.controller('innerController', innerController);
innerController.$inject = ['$scope'];
function innerController($scope) {
$scope.doSomething = doSomething;
function doSomething() {
console.log('doSomething()');
var obj = {a:1,b:2,c:3}; // <-- Build your params here
$scope.callAction({val1: 1, params: obj});
}
}
myApp.directive('inner', innerDirective );
function innerDirective() {
return {
'restrict': 'E',
'template': '{{label}}: <button ng-click="doSomething()">Do Something</button><br/>',
'controller': 'innerController',
'scope': {
callAction: '&',
label: '#'
}
};
}
</script>
</head>
<body ng-controller="appController">
<inner label="One Param" call-action="handleAction1(params)"></inner>
<inner label="Two Params" call-action="handleAction2(params, val)"></inner>
</body>
</html>
In the appController I have two functions that will be called by the inner directive. The directive is expecting the outer controller to pass in those functions using the call-action attribute on the <inner> tag.
When you click on the button within the inner directive it called the function $scope.doSomething This, in turn calls to the outer controller function handleAction1 or handleAction2. It also passes a set of parameters val1 and params:
$scope.callAction({val1: 1, params: obj});
In your template you specify which of those parameters you want to be passed into your outer controller function:
call-action="handleAction1(params)"
or
call-action="handleAction2(params, val)"
Angular then uses those parameter names to look into the object you sent when you called $scope.callAction.
If you need other parameters passed into the outer controller function then just add then into the object defined in the call to $scope.callAction. In your case you would want to put more content into the object you pass in:
var obj = {a:1,b:2,c:3}; // <-- Build your params here
Make that fit your need and then in your outer controller you would take in params and it would be a copy of the object defined just above this paragraph.
It this is not what you were asking, let me know.
I have a quite hard time to build a (maybe non-trivial) directive for an SPA. Basically I need to be able to pour data into the directive from any controller and the directive is supposed to show a live graph.
I've read this post and I would like to use a shared object on the isolated scope of this directive anyway.
So I tried to do smth like this:
Wrapping template:
<div ng-controller="WrappingCtrl">
<timeline-chart d3API="d3API"><timeline-chart>
</div>
In the 'wrapping' controller:
$scope.d3API = {};
$scope.d3API.options = {}; //for d3Config
$scope.d3API.currentValue = 3; //asynchronous!!!
Finally to use the shared object d3API in the directive's link method I tried e.g. this:
//in the directive:
scope: { //nice, but does it help??
d3API: '='
}
and:
var data = [1, 2];
var updateTimeAxis = function() {
var newValue;
if (data.length) {
newValue = (data[data.length - 1] !== scope.d3API.currentValue) ? scope.d3API.currentValue : data[data.length - 1];
data.push(newValue);
} else {
console.warn('problem in updateTimeAxis: no data length');
}
};
To gain some simplicity for this question I've created a fiddle, note, that none of both are working:
http://jsfiddle.net/MalteFab/rp55vjc8/3/
http://jsfiddle.net/MalteFab/rp55vjc8/5/
The value in the directive's template is not updated - what am I doing wrong? Any help is appreciated.
Your fiddle mostly works, you just need to update your controller to use $timeout:
app.controller('anyCtrl', function($scope, $timeout) {
show('anyCtrl');
$scope.bound = {};
$timeout(function() {
$scope.bound.says = 'hello';
}, 200);
});
Forked fiddle: http://jsfiddle.net/wvt1f1zt/
Otherwise no digest occurs so angular doesn't know something changed. Based on what you're actual problem is, I'm assuming you're not using timeout vs $timeout, but if your coding style is to intermix angular with "normal" javascript, you may be running into the same kind of scenario.
A good article for reference for telling angular about what your doing is here: http://jimhoskins.com/2012/12/17/angularjs-and-apply.html
I have a ServiceA() in module A and variable item is used in html.
angular.module("ModuleA").service("ServiceA", function () {
var item=[];
this.get(){
item.push("A");
}
});
Controller.js
angular.module("ModuleX").controller("Ctrl", function (ServiceA) {
$scope.service=ServiceA;
});
HTML:
<h1>{{service.item}}
</h1>
i am trying to achieve inheritance using angularjs .service(),I want to make serviceA() in moduleA as base service and create a service serviceB() in moduleB and this should inherit base service(serviceA()) and update variable 'item' in serviceA().
Controller code and html code remains same.
Is it possible? Is this a good approach? Can we achieve inheritance/abstraction using angularjs .service()?
angular.module("ModuleB").service("serviceB", function (serviceA) {
serviceA.item="B";
});
Whenever you define a module, you should pass a empty array or
dependencies as the second argument. i.e. angular.module("ModuleA",
[]) or angular.module("ModuleA", ['someDependent']).
The service is in one module whereas the controller is in the other module. So, you need to create a relation between them angular.module("ModuleX", ["ModuleA"]).
You are not returning anything from the service and you have written the method in wrong way.
Plnkr
Working Code
angular.module("ModuleA", []).service("ServiceA", function () {
var item=[];
this.get = function(){
item.push("A");
return item;
}
});
angular.module("ModuleX", ["ModuleA"]).controller("Ctrl", function ($scope, ServiceA) {
$scope.service = ServiceA.get();
console.log($scope);
});
I've created my own angular directive which will add and remove a class based off of a condition passed into it like below:
app.directive("alerter", function ($interval, $compile) {
return {
restrict: "A",
link: function ($scope, elem, attrs) {
var loginExpired = attrs.alerter;
var addClassInterval = undefined;
var timer = 3000;
var className = "alert";
addClassInterval = $interval(addClass, timer);
function addClass() {
if (elem.hasClass(className)) {
elem.removeClass(className);
} else if (loginExpired) {
elem.addClass(className);
}
$compile(elem)($scope);
}
}
}
});
This can then be used on an element as an attribute like below:
<div alerter="{{model.loginTime > model.expiryTime}}"> ... </div>
However, even when alerter evaluates to false, it still adds the class, but I'm not sure why? Also the $interval seems to not be working as intended, here is a Plunker I've created to demonstrate:
http://plnkr.co/edit/YKb6YARLaBnsevuoxv3G?p=preview
Thanks!
Edit
When I remove $compile(elem)($scope); it fixes the issue I was having with $interval however, I know that one of the conditions passed in is always false but it still applies the class to it
If you would like to parse the values via attr you will need to parse the value of attr.alerter as it is coming in a string (not a boolean)
you can use this code to do this:
var loginExpired = $parse(attrs.alerter)();
Note: you will need to inject in the $parse service
Another (less optimal solution) is to just do a string comparison of attrs.alerter === 'true'
var loginExpired = attrs.alerter;
this will always be true because you are getting the attribute value which is "true" or "false" both not empty strings hence always truthy if you want to use attrs and not an isolated scope then you'll have to check something like
var loginExpired = /true/.test(attrs.alerter);
I know that I can get access to the click event from ng-click if I pass in the $event object like so:
<button ng-click="myFunction($event)">Give me the $event</button>
<script>
function myFunction (event) {
typeof event !== "undefined" // true
}
</script>
It's a little bit annoying having to pass $event explicitly every time. Is it possible to set ng-click to somehow pass it to the function by default?
Take a peek at the ng-click directive source:
...
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function(scope, element, attr) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
It shows how the event object is being passed on to the ng-click expression, using $event as a name of the parameter. This is done by the $parse service, which doesn't allow for the parameters to bleed into the target scope, which means the answer is no, you can't access the $event object any other way but through the callback parameter.
Add a $event to the ng-click, for example:
<button type="button" ng-click="saveOffer($event)" accesskey="S"></button>
Then the jQuery.Event was passed to the callback:
As others said, you can't actually strictly do what you are asking for. That said, all of the tools available to the angular framework are actually available to you as well! What that means is you can actually write your own elements and provide this feature yourself. I wrote one of these up as an example which you can see at the following plunkr (http://plnkr.co/edit/Qrz9zFjc7Ud6KQoNMEI1).
The key parts of this are that I define a "clickable" element (don't do this if you need older IE support). In code that looks like:
<clickable>
<h1>Hello World!</h1>
</clickable>
Then I defined a directive to take this clickable element and turn it into what I want (something that automatically sets up my click event):
app.directive('clickable', function() {
return {
transclude: true,
restrict: 'E',
template: '<div ng-transclude ng-click="handleClick($event)"></div>'
};
});
Finally in my controller I have the click event ready to go:
$scope.handleClick = function($event) {
var i = 0;
};
Now, its worth stating that this hard codes the name of the method that handles the click event. If you wanted to eliminate this, you should be able to provide the directive with the name of your click handler and "tada" - you have an element (or attribute) that you can use and never have to inject "$event" again.
Hope that helps!
I wouldn't recommend doing this, but you can override the ngClick directive to do what you are looking for. That's not saying, you should.
With the original implementation in mind:
compile: function($element, attr) {
var fn = $parse(attr[directiveName]);
return function(scope, element, attr) {
element.on(lowercase(name), function(event) {
scope.$apply(function() {
fn(scope, {$event:event});
});
});
};
}
We can do this to override it:
// Go into your config block and inject $provide.
app.config(function ($provide) {
// Decorate the ngClick directive.
$provide.decorator('ngClickDirective', function ($delegate) {
// Grab the actual directive from the returned $delegate array.
var directive = $delegate[0];
// Stow away the original compile function of the ngClick directive.
var origCompile = directive.compile;
// Overwrite the original compile function.
directive.compile = function (el, attrs) {
// Apply the original compile function.
origCompile.apply(this, arguments);
// Return a new link function with our custom behaviour.
return function (scope, el, attrs) {
// Get the name of the passed in function.
var fn = attrs.ngClick;
el.on('click', function (event) {
scope.$apply(function () {
// If no property on scope matches the passed in fn, return.
if (!scope[fn]) {
return;
}
// Throw an error if we misused the new ngClick directive.
if (typeof scope[fn] !== 'function') {
throw new Error('Property ' + fn + ' is not a function on ' + scope);
}
// Call the passed in function with the event.
scope[fn].call(null, event);
});
});
};
};
return $delegate;
});
});
Then you'd pass in your functions like this:
<div ng-click="func"></div>
as opposed to:
<div ng-click="func()"></div>
jsBin: http://jsbin.com/piwafeke/3/edit
Like I said, I would not recommend doing this but it's a proof of concept showing you that, yes - you can in fact overwrite/extend/augment the builtin angular behaviour to fit your needs. Without having to dig all that deep into the original implementation.
Do please use it with care, if you were to decide on going down this path (it's a lot of fun though).