Accessing controller from compile function in directives - javascript

I have searched all over and the only thing I can come up with is that I don't understand something fundamental about how the compile function works.
Here is what I have
angular.module("app", [])
.directive("foo", function(){
return {
scope: {},
controller: function() {
this.someValue = 23;
},
contollerAs: "ctrl",
compile: function(tElem, tAttrs) {
tElem.html("<h1>Data: {{ctrl.someValue}}</h1>");
},
template: '<h1>test</h1>'
}
});
This displays: Data: and does not seem to see the "someValue" variable.
However when I use scope instead of the controllerAs syntax, it works.
//this works fine
angular.module("app", [])
.directive("foo", function(){
return {
scope: {},
controller: function($scope) {
$scope.someValue = 23;
},
contollerAs: "ctrl",
compile: function(tElem, tAttrs) {
tElem.html("<h1>Data: {{someValue}}</h1>");
},
template: '<h1>test</h1>'
}
});
This displays: Data: 23
Is there something I am missing here? Am I even using compile correctly?
Manual seems less then helpful.

Because there is a typo. It is controllerAs, not contollerAs.
It is recommended to use template function instead of compile. This makes upgrading to components easier in future, also saves from problems - compile in the directive above won't work correctly if there's no dummy <h1>test</h1> template.

Related

Can you get element.text() from the from an ngBind expression?

I have a big long dumb directive using ngBindTemplate to accomplish this but if I could simply access the element.text() inside the expression I could essentially remove the directive.
Question: How do I access 'element.text()' from the ngBind expression?
<h1 ng-bind="worldMapCtrl.header.docs.intro || 'element.text()'">ABC</h1>
Don't think you can do it, sorry!
Did a test an was unable to access the element it self. If you do:
<h1 id="test" ng-bind="worldMapCtrl.header.docs.intro || this.$id">ABC</h1>
And then from the console do:
angular.element(document.getElementById('test')).scope()
And check the $id you will see that they are the same. Also if you do:
angular.element(document.getElementById('test'))
You will see that the innerHTML is blank even if the ng-bind failed.
I made a not that long directive, that solves what you want, in case you still want to use a directive, let me know if this work for you.
(function(angular) {
'use strict';
angular.module('app', [])
.directive('dumbDirective', [function () {
return {
restrict: 'AE',
scope: {
htmlInput: '='
},
controller: SiteMenuController,
controllerAs: 'vm',
bindToController: true,
templateUrl: 'dumb-directive-template.html'
};
function SiteMenuController() {
this.text = angular.element(this.htmlInput).text();
}
}])
.run(['$templateCache', function($templateCache) {
$templateCache.put('dumb-directive-template.html',
'{{ vm.text }}'
);
}]);
})(window.angular);
Directive usage:
// outputs just 'testing'
<dumb-directive html-input="'<span>testing</span>'"></dumb-directive>

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.

Injecting services into AngularJS directive controller directly

I understand how Angular dependency injection works with directives but wanted clarification on something. I have a dummy test directive as follows:
app.directive("test", [
function() {
return {
restrict: "E",
scope: {},
controller: ["$scope", "$filter",
function($scope, $filter) {
var food = ["Apple pie", "Apple cobler", "Banana Split", "Cherry Pie", "Applesauce"];
$scope.favorites = $filter('filter')(food, "Apple");
}
],
template: "<div>{{favorites}}</div>"
}
}
]);
This works fine and will filter the food array as expected. However I noticed if I inject the $filter service in the directive as follows, it still works:
app.directive("test", ["$filter",
function($filter) {
return {
restrict: "E",
scope: {},
controller: ["$scope",function($scope) {...
My question: Is it better practice to inject services into a directive in the declaration line like so:
app.directive("test", ["$filter", function($filter) {
or in the controller line like this:
controller: ["$scope", "$filter", function($scope, $filter) {?
Is there no difference? Here is a Plunker of the directive code.
In this case, you're receiving the same service, so it likely doesn't matter too much. I personally prefer to keep them as localized as possible; if you don't need $filter in the link function or something like that, I'd just inject it into the controller.
In certain cases, this may also make it easier to mock dependencies during testing.
You can do this also. Much better way by splitting directive and its controller in a single file. Or you can write in separate files. But, better understand
app.directive('throbberDirective',
[
function(){
return {
restrict: "EA",
templateUrl: "common/utils/throbbers/throbber.html",
controller: throbberController
}
}
]);
app.controller('throbberController', throbberController);
throbberController.$inject = ['$scope', '_$ajax'];
function throbberController($scope){
$scope.throbber = _$ajax.getThrobberConfigs();
$scope.throbber.templateName = $scope.throbber.templateName;
}

$scope.item in directive is undefined

I have a problem with my directive and controller. The variable item in scope is undefined in directive, even though I passed it in html. This is my code:
app.js:
var app = angular.module("app", ["ngRoute"]);
app.config(["$routeProvider", function($routeProvider) {
$routeProvider
.when("/", {
templateUrl: "views/main.html",
controller: "MainCtrl"
});
}]);
controller.js:
app.controller("MainCtrl", function($scope) {
$scope.item = "x";
});
directive.js:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
templateUrl: "views/item.html"
};
});
index.html:
<div ng-view></div>
main.html:
<div example-directive item="item"></div>
item.html:
<div>{{ item }}</div>
UPDATE
I changed my code to:
app.directive("exampleDirective", function() {
return {
restrict: "A",
scope: true,
templateUrl: "views/item.html"
};
});
and now there is "x" in scope.$parent.item. But why it isn't present inside directive?
Although it seems to work just fine with latest Angular stable http://plnkr.co/edit/6oXDIF6P04FXZB335voR?p=preview maybe you are trying to use templateUrl thats pointing to somewhere it doesn't exist.
Another thing, use primitives only when strictly needed. In case you ever need to modify value of item, you won't be able to do so since you are using a primitive. Plus, if you need "more info" to go inside your isolated scopes, and to avoid attribute soup (attr-this="that", attr-that="boop", my-otherstuff="anotheritem.member", etc) you can pass the two-way bind of an object that handle more data.
Or if you need to share state through multiple controllers, directives, etc, use a service instead and use dependency injection, and there's no need to pass in objects/primitives to your isolated scope, and you can assure state, and that's best practice "the Angular way".
This fiddle works: http://jsfiddle.net/HB7LU/2844/ which is essentially the same thing just without the route information.
var myApp = angular.module('myApp',[]);
myApp.directive("exampleDirective", function() {
return {
restrict: "A",
scope: {
item: "="
},
template: "<div>{{ item }}</div>"
};
});
function MyCtrl($scope) {
$scope.item = 'Superhero';
}
With the view:
<div ng-controller="MyCtrl">
<div example-directive item="item"></div>
</div>
This leads me to believe it could be a scope issue. So try encapsulating the controller scope variable in a container:
$scope.cont = { item: 'x' };
And in the view
<div example-directive item="cont.item"></div>

Compile directives via service in angularjs

I'm trying to compile directive via angular service but unfortunately it doesn't work.
The idea is to show errors in popups.
I've modified $exceptionHandler service:
crm.factory('$exceptionHandler', function(popup) {
return function(exception) {
popup.open({message: exception});
}
});
The popup service looks like this:
crm.factory('popup', function ($document) {
return {
open: function (data) {
var injector = angular.element(document).injector(),
$compile = injector.get('$compile'),
template = angular.element('<popup></popup>');
// var ctmp = $compile(template.contents());
$compile(template.contents());
$document.find('body').append(template);
}
};
});
And I don't think that this was a good idea to hard code $compile service (but I haven't any ideas how to realize this in angular):
$compile = injector.get('$compile')
Popup directive:
crm.directive('popup', function () {
return {
restrict: 'E',
replace: true,
templateUrl: '/public/js/templates/common/popup.html',
link: function() {
console.log('link()');
},
controller: function () {
console.log('ctrl()');
}
};
});
May be there are some other ways to do this?
Thanks.
You can inject $compile directly into your service, also you're not quite using $compile correctly:
//commented alternative lines for allowing injection and minification since reflection on the minified code won't work
//crm.factory('popup', ['$document', '$compile', function ($document, $compile) {
crm.factory('popup', function ($document, $compile) {
return {
open: function (data) {
var template = angular.element('<popup></popup>'),
compiled = $compile(template);
$document.find('body').append(compiled);
}
};
});
//closing bracket for alternative definition that allows minification
//}]);

Categories

Resources