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>
Related
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.
I parse JSON objects to create html elements. In my case, I create from json file Buttons:
{
"type": "button",
"id": "comButton",
"icon": "ion-chatboxes",
"name": "Communication",
"onclick": "",
"controller": "somemthctrl",
"ngclick": "launchSomemethod()",
"color": "white",
"backgroundcolor": "#ff5db1",
"font-size": "20px"
}
Controller:
myApp.controller('generateButtonCtrl', function ($scope, $http) {
$http.get('JSON/buttons.json').success(function(data){
$scope.components = data;
});
});
From the HTML page, I call the components from the json file:
<a ng-repeat="component in components"
style="color:{{component.color}}; background-color:{{component.backgroundcolor}} "
id="{{component.id}}"
class="{{component.type}}"
href="{{component.onclick}}"
ng-click="{{component.ngclick}}"
ng-controller="{{component.controller}}">
<i class="{{component.icon}}"><br></i>
{{component.name}}
</a>
In the case ng-click="{{component.ngclick}}" und ng-controller="{{component.controller}}" will not be included.
At the appropriate places I get from my editor WebStorm following error: Identifier or String literal or numeric literal expected.
I have a {{expression}} Problem. How can I integrate the ng-controller and ng-click as a string from a json object?
This is quite tricky. Angular team on their doc suggests that controller should be registered to a module while the DOM is being parsed.
All the $scope properties will be available to the template at the point in the DOM where the Controller is registered.
Link: Angular controllers
Use $controllerProvider to register the controller This link
shed's some light on how controllers are resolved at later point of
the time, which might help you in designing your code as desired.
ng-click you can give an expression or a method which is inside the controller of $scope tree. Both the expression and/or function
search happens inside the $scope tree at the compile time of your
template.
Update As per the fiddler requested
I have corrected the fiddler code, removed unwanted lines, errors and made it working.
Now your template is dynamic binding to the JavaScript code at the run-time.
Fiddler Link
Javascript:
var myApp = angular.module('starter', []);
myApp.config(['$sceDelegateProvider', function($sceDelegateProvider) {
$sceDelegateProvider.resourceUrlWhitelist([
'self',
'https://api.myjson.com/**'
]);
}]);
myApp.controller('generateHTMLCtrl', function ($scope, $http, $compile, $interpolate, $templateCache) {
$http.get('https://api.myjson.com/bins/1gkh0').success(function (data) {
for(var i in data){
var interpolated = $interpolate($templateCache.get("tpl").trim())(data[i]);
angular.element(document.querySelector("#loadhere")).append($compile(interpolated)($scope));
}
});
});
myApp.controller("OpenLinkCtrl", function ($scope) {
$scope.OpenLink = function () {
alert("Link open");
}
});
Html:
<body ng-app="starter" class="padding" style="text-align: center">
<div class="row responsive-md" ng-controller="generateHTMLCtrl" id="loadhere"></div>
<script type="text/ng-template" id="tpl">
<div class="col">
<a style="color:{{color}}; background-color:{{backgroundcolor}} "
id="{{id}}" class="{{type}}" href="{{topage}}" ng-controller="{{controller}}" ng-click="{{function}}"><i class="{{icon}}"><br></i>{{name}}</a>
</div>
</script>
</body>
Explanation:
Used interpolate service
Used compiler service
Note: Interpolator cannot parse on array of objects. Hence used for loop to interpolate each oject array and append it to the DOM.
More info on compiler and interpolation can be found here
I'm not sure that it's possible the ng-controller could be a {{ expression }}.
The workarround it's creating a function that returns the name of the controller that you want and you can assign to a var inside the controller...
In other case, why you want to use a different controller for each button?
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.
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
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