angular component functionality with $window - angularjs-directive

Whats wrong with this angular component:
https://embed.plnkr.co/yMYfWk/
My original functionality works well with directives:
https://embed.plnkr.co/4nFKnM/
I believe this has something to do with $window injection

Assign the showCollapsed variable to the controller function body, not $scope: ctrl.showCollapsed = true;
rewrite the require property of the children like: require: { nsCollapsibleHeader: '^nsCollapsibleHeader' },
use the showCollapsed variable in your template like: ng-hide="$ctrl.nsCollapsibleHeader.showCollapsed"
Here is a working plunker.

Related

AngularJS directive with method called from outside

I created a directive with a method that should be called from other elements that are not part of the directive. However it looks like this method is not exposed.
Some example jade code to clarify:
//- a controller for the view itself
div(ng-controller="someController")
//- this is part of the view itself, not within the directive
div(ng-repeat="element in elements")
div(ng-click="methodFromDirective(element)") click element {{$index}} to trigger directive
//- this is the directive
div(some-directive)
The someController isn't too important here I think. It has methods but NOT the methodFromDirective(element) one. The methodFromDirective(element) is a method that exists only in the directive.
If I make a directive and put some logging on creation I can clearly see it's created. However the methodFromDirective(element) method isn't exposed so the calls aren't properly triggered.
The methodFromDirective(element) itself will only work on elements from within the directive's template.
some coffeescript to show the definition of the the directive (ignore indentation errors here):
'use strict'
define [], () ->
someDirective = () ->
restrict: 'A'
scope: {
show: '='
}
transclude: false
templateUrl: 'someTemplateHere.html'
controller = ($scope) ->
# exposing the method here
$scope.methodFromDirective(element)->
$scope.theMethod element
link = (scope, element, attr) ->
# this is logged
console.log "init someDirective"
# triggering this method form outside fails
scope.theMethod = (element)->
console.log "method triggered with element", JSON.stringify(element)
I found my issue.
From the angularJS documentation on directives I was looking into the transclude option since that states:
What does this transclude option do, exactly? transclude makes the contents of a directive with this option have access to the scope outside of the directive rather than inside.
I combined transclude=false with the controller function since that exposes the method, again from docs:
Savvy readers may be wondering what the difference is between link and controller. The basic difference is that controller can expose an API, and link functions can interact with controllers using require.
However what I missed completely was that I isolated scope within my directive. From docs:
What we want to be able to do is separate the scope inside a directive from the scope outside, and then map the outer scope to a directive's inner scope. We can do this by creating what we call an isolate scope. To do this, we can use a directive's scope option:
So even if you use transclude=false and the controller function you'll still fail to expose methods if you use isolated scope! Lesson learned!
While figuring out what went wrong I also made a fiddle for better understanding: http://jsfiddle.net/qyBEr/1/
html
<div ng-app="directiveScopeExample">
<div ng-controller="Ctrl1">
<p>see if we can trigger a method form the controller that exists in the directive.</p>
<ul>
<li>Method in Controller</li>
<li>Method in Directive</li>
</ul>
<simple-directive/>
</div>
</div>
javascript
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
transclude: false,
controller: function($scope){
$scope.methodInDirective = function(){
// call a method that is defined on scope but only within the directive, this is exposed beause defined within the link function on the $scope
$scope.showMessage('Method in directive triggered');
}
}
// this is the issue, creating a new scope prevents the controller to call the methods from the directive
//, scope: {
// title: '#'
//}
, link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
})
Calling private methods inside directive's link function is very simple
dropOffScope = $('#drop_off_date').scope();
dropOffScope.setMinDate('11/10/2014');
where
$('#drop_off_date') - jQuery function
setMinDate() - private function inside directive
You can call directive function even from outer space.
By default the scope on directive is false meaning directive will use the parent's scope instead of creating a new one. And hence any function or model defined in the directive will be accessible in the parent scope. Check out this.
I think your problem can be solved as follows:
angular.module('directiveScopeExample', [])
.controller('Ctrl1', function Ctrl1($scope) {
$scope.methodInController = function(){
alert('Method in controller triggered');
};
})
.directive('simpleDirective', function(){
return {
restrict: 'E',
scope: false,
link: function(scope, element, attrs, tabsCtrl) {
// view related code here
scope.showMessage = function(message){
alert(message);
}
},
//templateUrl: 'some-template-here.html'
};
This approach might be an issue in case you want to create reusable directives and you are maintaining some state/models in your directive scope. But since you are just creating functions without side-effects, you should be fine.

Can I create a directive without a module?

I typically have been defining my controllers as such:
<body data-ng-app>
<div data-ng-controller='IndexCtrl'>
</div>
</body>
so my controller is defined:
var indexController = function($scope) { /* ... */ }
can I do the same with directives? it seems I have to go an name the ng-app to create a directive?
"Can I create a directive without a module?"
No. I'm pretty sure Angular requires directives be in modules. And then you need to have the module loaded to consume the directive. So if you do angular.module('myApp', []) as your main module and you want to use a library 'myLib' then you /have/ to make the main module depend on myLib somehow (i.e. angular.module('myApp', ['myLib']).
"it seems I have to go an name the ng-app to create a directive"
Not exactly. If you use an already-existing-in-vanilla-angular module, you could (although I don't know why you want to do this in the first place) do this:
Javascript
angular.module('ng').directive('sample', function () {
return {
restrict: 'E',
template: '<b> YAYYYYYY </b>'
}
});
Template
<sample></sample>

Two way binding Angularjs directives isn't working

I have been trying to figure out the solution but I think i hit a dead end.
So here is my directive
directives.directive('postprocess', function($compile)
{
return {
restrict : 'E',
require: '^ngModel',
scope: {
ngModel: '='
},
link: function(scope, element, attrs) {
var parsed = scope.ngModel;
el = $compile(parsed)(scope);
element.html("");
//add some other html entities/styles.
element.append(el);
console.log(parsed);
}
};
});
The html
<postprocess ng-model="some_model.its_property" style="padding-top: 10px;" />
Somewhere in the controller, I update the model property
some_model.its_property = 'Holla';
But it doesn't update the corresponding directive. It works perfectly when loading which tells me that it might not be entirely a scoping issue.
It's much simpler, so I have removed some extra code you had there.
Please take a look at the code below or working Plunker:
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="UTF-8">
<title>Document</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.min.js"></script>
<script>
var myApp = angular.module('myApp', []);
myApp.directive('postprocess', function ($timeout) {
return {
restrict : 'E',
transclude: 'true',
scope: {
myVariable: '='
},
link: function(scope, element, attrs) {
$timeout(function () {
scope.myVariable = 'Bye bye!'
}, 200);
}
};
});
myApp.controller('myAppCtrl', ['$scope', '$timeout', function ($scope, $timeout) {
$scope.myVariable = {
value : 'Holla'
};
console.log($scope.myVariable.value); // -> prints initial value
$timeout(function () {
console.log($scope.myVariable.value); // -> prints value after it is changed by the directive
}, 2000);
}])
</script>
</head>
<body ng-controller="myAppCtrl">
<postprocess my-variable="myVariable.value" style="padding-top: 10px;" />
</body>
</html>
The controller sets the initial value to 'Holla'
The directive receives that value by the my-variable attribute
Using two way data-binding any changes made to scope.myVariable updates the $scope.myVariable of the main controller
After few seconds $scope.myVariable changes to 'Bye Bye'
Take a look at your console.log
$watch and $apply
Angular's two-way data binding is the root of all awesome in Angular. However, it's not magic, and there are some situations where you need to give it a nudge in the right direction.
When you bind a value to an element in Angular using ng-model, ng-repeat, etc., Angular creates a $watch on that value. Then whenever a value on a scope changes, all $watches observing that element are executed, and everything updates.
Sometimes, usually when you're writing a custom directive, you will have to define your own $watch on a scope value to make the directive react to changes.
On the flip side, sometimes you change a scope value in some code but the app doesn't react to it. Angular checks for scope variable changes after pieces of your code have finished running; for example, when ng-click calls a function on your scope, Angular will check for changes and react. However, some code is outside of Angular and you'll have to call scope.$apply() yourself to trigger the update. This is most commonly seen in event handlers in custom directives.
Some help from angularjs irc, & dluz, updated. Though I wish there was an easier way for the directive to be called, since the link function contains behavior and there should be a way to call that.
http://jsfiddle.net/T7cqV/5/
be sure that you use the dot rule
http://jimhoskins.com/2012/12/14/nested-scopes-in-angularjs.html

With ng-bind-html-unsafe removed, how do I inject HTML?

I'm trying to use $sanitize provider and the ng-bind-htm-unsafe directive to allow my controller to inject HTML into a DIV.
However, I can't get it to work.
<div ng-bind-html-unsafe="{{preview_data.preview.embed.html}}"></div>
I discovered that it is because it was removed from AngularJS (thanks).
But without ng-bind-html-unsafe, I get this error:
http://errors.angularjs.org/undefined/$sce/unsafe
Instead of declaring a function in your scope, as suggested by Alex, you can convert it to a simple filter :
angular.module('myApp')
.filter('to_trusted', ['$sce', function($sce){
return function(text) {
return $sce.trustAsHtml(text);
};
}]);
Then you can use it like this :
<div ng-bind-html="preview_data.preview.embed.html | to_trusted"></div>
And here is a working example : http://jsfiddle.net/leeroy/6j4Lg/1/
You indicated that you're using Angular 1.2.0... as one of the other comments indicated, ng-bind-html-unsafe has been deprecated.
Instead, you'll want to do something like this:
<div ng-bind-html="preview_data.preview.embed.htmlSafe"></div>
In your controller, inject the $sce service, and mark the HTML as "trusted":
myApp.controller('myCtrl', ['$scope', '$sce', function($scope, $sce) {
// ...
$scope.preview_data.preview.embed.htmlSafe =
$sce.trustAsHtml(preview_data.preview.embed.html);
}
Note that you'll want to be using 1.2.0-rc3 or newer. (They fixed a bug in rc3 that prevented "watchers" from working properly on trusted HTML.)
You need to make sure that sanitize.js is loaded. For example, load it from https://ajax.googleapis.com/ajax/libs/angularjs/[LAST_VERSION]/angular-sanitize.min.js
you need to include ngSanitize module on your app
eg: var app = angular.module('myApp', ['ngSanitize']);
you just need to bind with ng-bind-html the original html content. No need to do anything else in your controller. The parsing and conversion is automatically done by the ngBindHtml directive. (Read the How does it work section on this: $sce). So, in your case <div ng-bind-html="preview_data.preview.embed.html"></div> would do the work.
For me, the simplest and most flexible solution is:
<div ng-bind-html="to_trusted(preview_data.preview.embed.html)"></div>
And add function to your controller:
$scope.to_trusted = function(html_code) {
return $sce.trustAsHtml(html_code);
}
Don't forget add $sce to your controller's initialization.
The best solution to this in my opinion is this:
Create a custom filter which can be in a common.module.js file for example - used through out your app:
var app = angular.module('common.module', []);
// html filter (render text as html)
app.filter('html', ['$sce', function ($sce) {
return function (text) {
return $sce.trustAsHtml(text);
};
}])
Usage:
<span ng-bind-html="yourDataValue | html"></span>
Now - I don't see why the directive ng-bind-html does not trustAsHtml as part of its function - seems a bit daft to me that it doesn't
Anyway - that's the way I do it - 67% of the time, it works ever time.
You can create your own simple unsafe html binding, of course if you use user input it could be a security risk.
App.directive('simpleHtml', function() {
return function(scope, element, attr) {
scope.$watch(attr.simpleHtml, function (value) {
element.html(scope.$eval(attr.simpleHtml));
})
};
})
You do not need to use {{ }} inside of ng-bind-html-unsafe:
<div ng-bind-html-unsafe="preview_data.preview.embed.html"></div>
Here's an example: http://plnkr.co/edit/R7JmGIo4xcJoBc1v4iki?p=preview
The {{ }} operator is essentially just a shorthand for ng-bind, so what you were trying amounts to a binding inside a binding, which doesn't work.
I've had a similar problem. Still couldn't get content from my markdown files hosted on github.
After setting up a whitelist (with added github domain) to the $sceDelegateProvider in app.js it worked like a charm.
Description: Using a whitelist instead of wrapping as trusted if you load content from a different urls.
Docs: $sceDelegateProvider and ngInclude (for fetching, compiling and including external HTML fragment)
Strict Contextual Escaping can be disabled entirely, allowing you to inject html using ng-html-bind. This is an unsafe option, but helpful when testing.
Example from the AngularJS documentation on $sce:
angular.module('myAppWithSceDisabledmyApp', []).config(function($sceProvider) {
// Completely disable SCE. For demonstration purposes only!
// Do not use in new projects.
$sceProvider.enabled(false);
});
Attaching the above config section to your app will allow you inject html into ng-html-bind, but as the doc remarks:
SCE gives you a lot of security benefits for little coding overhead.
It will be much harder to take an SCE disabled application and either
secure it on your own or enable SCE at a later stage. It might make
sense to disable SCE for cases where you have a lot of existing code
that was written before SCE was introduced and you're migrating them a
module at a time.
You can use filter like this
angular.module('app').filter('trustAs', ['$sce',
function($sce) {
return function (input, type) {
if (typeof input === "string") {
return $sce.trustAs(type || 'html', input);
}
console.log("trustAs filter. Error. input isn't a string");
return "";
};
}
]);
usage
<div ng-bind-html="myData | trustAs"></div>
it can be used for other resource types, for example source link for iframes and other types declared here

Scope's eval returns undefined in an AngularJS directive

Background:
I'm trying to run a callback when something inside the code of a directive in AngularJS happen.
Pertinent code:
HTML:
<img-cropper onselect="updateAvatarData" x="100" y="100" src="{{tempAvatar}}" id="avatarCropper"/>
Controller:
$scope.updateAvatarData = function(c){
alert("¡¡¡Funciona!!!!");
};
Directive:
<<more code up here>>
link: function(scope,element, attr) {
scope.wsId = attr.id+'WS';
scope.pvId = attr.id+'preview';
scope.x = attr.x;
scope.y = attr.y;
scope.aspectRatio = scope.x/scope.y;
scope.previewStyle = "width:"+scope.x+"px;height:"+scope.y+"px;overflow:hidden;";
scope.onSelectFn = scope.$eval(attr.onselect);
<<more code down here>>
The problem is in that last line "scope.onSelectFn = scope.$eval(attr.onselect);". That "scope.$eval(attr.onselect);" returns 'undefined'. The attr.onselect works fine, it returns the name of the function typed on the 'onselect' attribute.
I have made others directives with functions passed via attibutes with no problem, but am unable to find what I am doing wrong here.
Thanks in advance
Why you are doing like this when u can easily use '&' feature available with angular
calling method of parent controller from a directive in AngularJS
Still if you want to call parent function like this then you should be using $parse instead of eval see a very below small example when using
link: function (scope,element,attrs) {
var parentGet = $parse(attrs['onselect']);
var fn = parentGet(scope.$parent);
fn();
},
scope.$eval(attr.onselect) should work.
Here is a working fiddle (tested in Chrome) with a minimal link function:
link: function(scope, element, attr) {
scope.onSelectFn = scope.$eval(attr.onselect);
console.log(attr.onselect, ',', scope.onSelectFn);
scope.onSelectFn();
},
The only other thing I can think of is that since onselect is an HTML attribute, maybe it doesn't work on some other browsers. So maybe try using a different attribute name.
By default, $eval only evaluates the given expression against the current scope. You can pass in a different data object to evaluate against, and in your case it is the parent scope. You should call it like this:
scope.onSelectFn = scope.$eval(attr.onselect, scope.$parent);
See the documentation here