I am trying to figure out how AngularJS works (or should work). I've got in mind a very simple app with a header, main content and footer. Main content has a mainCtrl that holds most data (no service at this stage). I built header and footer as directives, outside the main content div. Is it possible (or recommended) to access and alter (bind) the mainCtrl variables from my directives? I read that controller and controllerAs, create a copy of the controller, while require might be what I want. But I can't get it to work. Maybe some info and pointing to the right direction could help.
PS Is it much better to have each controller get data using a service/factory? If yes, wouldn't it be efficient to store some data in the controller instead of calling the service all the time?
Please let me know if you need any clarification.
*EDIT: Demo here: plunker Demo
<!DOCTYPE html>
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>My AngularJS App</title>
<meta name="description" content="">
<link rel="stylesheet" href="bower_components/angular-material/angular-material.css">
<link rel="stylesheet" href="app.css">
</head>
<body ng-app="menuApp">
<div header-directive></div>
<hr/>
<div style="min-height: calc(100vh - 400px)" ng-controller="MainController as main">
<p>Direct access to (mainCtrl) SCOPE user: {{ user }}</p>
<p>Direct access to mainCtrl user: {{ main.user }}</p>
<ul>
<li ng-repeat="item in main.menuItems"><a ng-href="#/{{ item.toLowerCase() }}">{{ item }}</a></li>
</ul>
<!--<img src="http://ost2.gr/files/gimgs/1_random2.png">-->
<ng-view></ng-view>
</div>
<div footer-directive></div>
Below footer: {{ main.user }}
<script src="bower_components/angular/angular.js"></script>
<script src="bower_components/angular-animate/angular-animate.js"></script>
<script src="bower_components/angular-material/angular-material.js"></script>
<script src="bower_components/angular-aria/angular-aria.js"></script>
<script src="bower_components/angular-route/angular-route.js"></script>
<script src="app.js"></script>
<script src="menuApp.config.js"></script>
<script src="mainController.js"></script>
<script src="ProductsController.js"></script>
<script src="ChartsController.js"></script>
<script src="AboutController.js"></script>
<script src="footerDirective.js"></script>
<script src="headerDirective.js"></script>
</body>
</html>
Main Controller:
(function () {
'use strict';
angular
.module('menuApp')
.controller('MainController', MainController);
MainController.$inject = ['$scope'];
/* #ngInject */
function MainController($scope) {
/* jshint validthis: true */
var vm = this;
vm.activate = activate;
vm.title = 'main';
activate();
////////////////
function activate() {
vm.menuItems = ['Main', 'Products', 'Charts', 'About'];
$scope.user = 'SCOPE USER';
vm.user = 'user from MainController';
}
}
})();
header Directive:
(function () {
'use strict';
angular
.module('menuApp')
.directive('headerDirective', headerDirective);
headerDirective.$inject = ['$window'];
/* #ngInject */
function headerDirective($window) {
// Usage:
//
// Creates:
//
var directive = {
//link: link,
restrict: 'EA', //This means that it will be used as an attribute and NOT as an element. I don't like creating custom HTML elements
replace: true,
templateUrl: "header.html"
//require: '^MainController'
//scope: { user: '=' }
,controller: 'MainController'
,controllerAs: 'main'
};
return directive;
//function link(scope, element, attrs) {
//}
}
})();
Footer Directive:
(function () {
'use strict';
angular
.module('menuApp')
.directive('footerDirective', footerDirective);
footerDirective.$inject = ['$window'];
/* #ngInject */
function footerDirective($window) {
// Usage:
//
// Creates:
//
var directive = {
//link: link,
restrict: 'EA',
replace: true,
templateUrl: 'footer.html',
require: "^headerDirective"
//scope: {'main.user': '='}, // This is one of the cool things :). Will be explained in post.
//,controller: 'MainController'
//,controllerAs: 'main'
};
return directive;
//function link(scope, element, attrs) {
//}
}
})();
Header template:
<div>
<p>Scope user: {{ user }} </p>
<p>Main user: {{ main.user }}</p>
<hr/>
<p>
This part of the header is always here
</p>
<p ng-if="user">
User is logged in :D
</p>
<p ng-if="!user">
Hey buddy, log in! Be cool
</p>
<hr/>
<p>Scope user: <input ng-model="user"> {{ user }}</p>
<p>Main user: <input ng-model="main.user"> {{ main.user }}</p>
</div>
Footer template:
<md-toolbar class="md-medium-tall">
<div layout="row" layout-align="center center" flex>
<span>FOOTER</span>
</div>
$SCOPE USER: {{ user }}
<hr/>
MAIN USER: {{ main.user }}
</md-toolbar>
If your directives sitting outside of the controller div, you will not be able to access the scope of your controller no matter how you will configure the directives scope.
You can use a service and inject the service to the the MainCtrl and to the directives or a good practices for your use case is to put a Parent controller that will be the global scope of your app. Then you configure your directives scope to inherit from the Parent controller
for example
<div ng-controller="ParentCtrl">
<div header-directive></div>
<div ng-controller="ChildCtrl">
</div>
<div footer-directive></div>
</div>
Related
I have a simple directive with transcluded html.
I want to be able to inject directive scope params to the transclude.
I wrote a simple example in plunker :
https://plnkr.co/edit/jqyiQdgQxbeTrzyidZYF?p=preview
I know in angular 4 it can be done, but I can't find a good way to do it in angularjs.
// Code goes here
var app = angular.module("app", []);
app.controller("mainCtrl", function($scope) {
$scope.users = ["tal", "oren", "orel", "shluki"];
$scope.deleteUser = (user) => {alert("trying to delete", user);}
});
app.directive('myList', function myList() {
return {
restrict: 'E',
transclude: true,
template: "<div><table><tr ng-repeat='item in collection'><td> This is inside myList - user name: {{item}} <ng-transclude></ng-transclude></td></tr></table></div>",
scope: {
collection: "="
},
replace: true
};
});
<!DOCTYPE html>
<html>
<head>
<script data-require="angularjs#1.6.2" data-semver="1.6.2" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.6.2/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body ng-app="app" ng-controller="mainCtrl">
<h1>Hello Plunker!</h1>
<my-list collection="users">
<h2>This is transclude</h2>
<button ng-click="deleteUser(user)">Delete user: {{user ? user : "User name should be here"}}</button>
</my-list>
</body>
</html>
Will really appreicate some help.
plunker: https://plnkr.co/edit/jqyiQdgQxbeTrzyidZYF?p=preview
Here's a working plunker with your example.
http://plnkr.co/edit/BjSowyQdLXd0xoCZFqZ6?p=preview
The idea is to pass it as contents and not html as string. $compile is here because the link is done after ng-repeats already has transcluded its own template.
var template = '<h1>I am foo</h1>\
<div ng-repeat="item in users">\
<placeholder></placeholder>\
<hr>\
</div>';
var templateEl = angular.element(template);
transclude(scope, function(clonedContent) {
templateEl.find("placeholder").replaceWith(clonedContent);
$compile(templateEl)(scope, function(clonedTemplate) {
element.append(clonedTemplate);
});
});
If you want a proper explanation of what the problem was you should check the detailed answer here : Pass data to transcluded element
Hope this helped you out
I try to implement a tooltip with angularjs template inside. For this, I use "uib-tooltip-html" and I add an attribute on the element to compile the template. But it doesn't work.
Here is the code
Here is the plunker
http://plnkr.co/edit/y1TvogsFFBoBVra3gO3F?p=preview
<html>
<head lang="en">
<meta charset="UTF-8"/>
<title>uib-tooltip-html test</title>
<link href="https://netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular.js"></script>
<script src="https://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.14.3.js"></script>
<script type="text/javascript" src="http://ajax.googleapis.com/ajax/libs/angularjs/1.4.1/angular-sanitize.min.js"></script>
<script>
var app = angular.module("test", ['ngSanitize','ui.bootstrap']).config(function($sceProvider) {
$sceProvider.enabled(false);
});
app.controller("testController", function($scope, $http, $interval, $sce) {
$scope.text = $sce.trustAsHtml('<table><tr ng-repeat="x in [1,2,3]"><td>{{ x }}</td></tr></table>');
});
app.directive('compileTemplate', function($compile, $parse){
return {
link: function(scope, element, attr){
var parsed = $parse(attr.uibTooltipHtml);
console.log(attr.uibTooltipHtml);
function getStringValue() { return (parsed(scope) || '').toString(); }
console.log(getStringValue())
//Recompile if the template changes
scope.$watch(getStringValue, function() {
console.log('ca passe');
$compile(element, null, -9999)(scope); //The -9999 makes it skip directives so that we do not recompile ourselves
});
}
}
});
</script>
</head>
<body>
<div ng-app="test" ng-controller="testController">
<p style="margin-top: 5em;" uib-tooltip="Some text" >
A Thing With a Tooltip
</p>
<p style="margin-top: 5em;" uib-tooltip-html="text" compile-template>
A Thing With an HTML Tooltip
</p>
</div>
Thank you in advance for your answer
You can use uib-tooltip-template like this:
<p style="margin-top: 5em;" uib-tooltip-template="'myTooltipTemplate.html'">
A Thing With an HTML Tooltip
</p>
And then in put your html in myTooltipTemplate.html:
<table><tr ng-repeat="x in [1,2,3]"><td>{{ x }}</td></tr></table>
The template goes in a separate file.
documentation: https://angular-ui.github.io/bootstrap/#/tooltip
plnkr: http://plnkr.co/edit/tiCHpd0LipixXbO4Xfa5?p=preview
I have used https://github.com/angular-ui/bootstrap for accordion, but with this directive I'm having scope issues. It only allows to use scope if ngController is declared inside of accordion.
Please visit below two Plunker links and you will understand what I'm trying to say:
Example 1: http://plnkr.co/edit/Fb4UauWWmHOnTyjMPFBo
index.html
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-animate.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.4.js"></script>
<script src="script.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="AccordionDemoCtrl">
<accordion>
<accordion-group is-open="status.open">
<accordion-heading>
Accordian - Click me <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</accordion-heading>
<div>
<input type="checkbox" ng-model="select" ng-click="checkAll()" /> Check me
</div>
</accordion-group>
</accordion>
</div>
</body>
</html>
script.js
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('AccordionDemoCtrl', function($scope) {
$scope.checkAll = function() {
alert($scope.select);
};
});
Example 2: http://plnkr.co/edit/ljEMUnTqPBqUyub5eEB7
index.html
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-animate.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.4.js"></script>
<script src="script.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<accordion>
<accordion-group is-open="status.open">
<accordion-heading>
Accordian - Click me <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</accordion-heading>
<div ng-controller="AccordionDemoCtrl">
<input type="checkbox" ng-model="select" ng-click="checkAll()" /> Check me
</div>
</accordion-group>
</accordion>
</body>
</html>
script.js
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('AccordionDemoCtrl', function($scope) {
$scope.checkAll = function() {
alert($scope.select);
};
});
I found the solution:
We can pass the value from the function itself, not necessary to access value using $scope.
Demo: http://plnkr.co/edit/fZZrDN4e8kbvimR2Wzya
index.html
<!doctype html>
<html ng-app="plunker">
<head>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-animate.js"></script>
<script src="//angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.13.4.js"></script>
<script src="script.js"></script>
<link href="//netdna.bootstrapcdn.com/bootstrap/3.1.1/css/bootstrap.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="AccordionDemoCtrl">
<accordion>
<accordion-group is-open="status.open">
<accordion-heading>
Accordian - Click me <i class="pull-right glyphicon" ng-class="{'glyphicon-chevron-down': status.open, 'glyphicon-chevron-right': !status.open}"></i>
</accordion-heading>
<div>
<input type="checkbox" ng-model="select" ng-click="checkAll(select)" /> Check me
</div>
</accordion-group>
</accordion>
</div>
</body>
</html>
script.js:
var app = angular.module('plunker', ['ui.bootstrap']);
app.controller('AccordionDemoCtrl', function($scope) {
$scope.checkAll = function (select) {
alert(select);
};
});
This is happening because the scope of directive "accordionGroup" in Angular Bootstrap is isolated and the $scope of controller "AccordionDemoCtrl" is inherited using require in a "directive".
So, for Example1 when you try to access the $scope binding of AccordionDemoCtrlinside the accordian group directive, it is easily accessible. But as the select is in local scope of accordian group directive it is not accessible in the controller.
So, for Example2 when you try to access the $scope binding of AccordionDemoCtrlinside the accordian group directive it is easily accessible as the controller is in the local scope for the accordian group.
Please refer directive to directive Comm.
This is code from the Angular Bootstrap Library for Accordian
.directive('accordion', function () {
return {
restrict:'EA',
controller:'AccordionController',
transclude: true,
replace: false,
templateUrl: 'template/accordion/accordion.html'
};
})
// The accordion-group directive indicates a block of html that will expand and collapse in an accordion
.directive('accordionGroup', function() {
return {
require:'^accordion', // We need this directive to be inside an accordion
restrict:'EA',
transclude:true, // It transcludes the contents of the directive into the template
replace: true, // The element containing the directive will be replaced with the template
templateUrl:'template/accordion/accordion-group.html',
scope: {
heading: '#', // Interpolate the heading attribute onto this scope
isOpen: '=?',
isDisabled: '=?'
},
controller: function() {
this.setHeading = function(element) {
this.heading = element;
};
},
link: function(scope, element, attrs, accordionCtrl) {
accordionCtrl.addGroup(scope);
scope.$watch('isOpen', function(value) {
if ( value ) {
accordionCtrl.closeOthers(scope);
}
});
scope.toggleOpen = function() {
if ( !scope.isDisabled ) {
scope.isOpen = !scope.isOpen;
}
};
}
};
})
// Use accordion-heading below an accordion-group to provide a heading containing HTML
// <accordion-group>
// <accordion-heading>Heading containing HTML - <img src="..."></accordion-heading>
// </accordion-group>
.directive('accordionHeading', function() {
return {
restrict: 'EA',
transclude: true, // Grab the contents to be used as the heading
template: '', // In effect remove this element!
replace: true,
require: '^accordionGroup',
link: function(scope, element, attr, accordionGroupCtrl, transclude) {
// Pass the heading to the accordion-group controller
// so that it can be transcluded into the right place in the template
// [The second parameter to transclude causes the elements to be cloned so that they work in ng-repeat]
accordionGroupCtrl.setHeading(transclude(scope, function() {}));
}
};
})
I've created an angular directive with a template similar to this:
<div>
<header class='header'></header>
<div class='subheader'>
<div ng-bind-html='subheaderTemplate'></div>
</div>
<div class='content' ng-transclude></div>
</div>
This directive will always be used within a parent div that has a controller on it. Each usage of the directive will pass in a different HTML snippet as the subheaderTemplate, from the parent controller. Some instances need this HTML snippet to use the controller from the parent container. But testing it out, the subheader template doesn't seem to know about the parent controller at all.
Here's a typical usage of the directive:
<div ng-controller="MyController as mc">
<my-directive subheader-template="mc.subheaderTemplate">
<div class="content">Content</div>
</my-directive>
</div>
Am I misunderstanding some behavior from ng-bind-html or something?
I think I understand what you are after and I think I got it to work in this plunker based on the example in the Angular docs. So it should work. Maybe you can provide more code if this example does not solve your problem.
(function(angular) {
'use strict';
angular.module('bindHtmlExample', ['ngSanitize'])
.controller('ExampleController', ['$scope', function($scope) {
$scope.header = '<h1>I am an <code>HTML</code>string with ' +
'links! and other <em>stuff</em></h1>';
}])
.directive('test', function() {
return {
restrict: 'E',
template: "<div><header class='header'></header><div class='subheader'><div ng-bind-html='subheaderTemplate'></div></div><div class='content' ng-transclude></div></div>",
scope: {
subheaderTemplate: '='
},
transclude: true
}
});
})(window.angular);
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Example - example-example62-production</title>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/angularjs/1.4.5/angular-sanitize.js"></script>
<script src="script.js"></script>
</head>
<body ng-app="bindHtmlExample">
<div ng-controller="ExampleController">
<p ng-bind-html="myHTML"></p>
<test subheader-template="header">
<h3>subheader</h3>
</test>
</div>
</body>
</html>
Plunkr
Below is my code
I created a simple page and include a directive in it. And on ng-click in directive i want to call parent scope's method.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>Directive Page</title>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.0/angular.min.js"> </script>
</head>
<body>
<div ng-app="myapp" ng-controller="myController">
<ul-dir on-item-click="itemClick(obj)"></ul-dir>
</div>
</body>
</html>
<script>
var myapp = angular.module("myapp", []);
myapp.directive('ulDir', function() {
return {
restrict: 'E',
replace: 'true',
template: '<div id="container"><ul><li ng-repeat="content in contents">{{content.name}}</li></ul></div>',
controller: function ($scope) {
$scope.contents = [{'name':'Nishu', 'age':'20'},{'name':'Nidhi', 'age':'21'},{'name':'Kirti', 'age':'24'}];
},
scope: {
onItemClick: '&' // Pass a reference to the method
}
}
});
myapp.controller('myController', function($scope) {
$scope.itemClick = function(content){
console.log("Success : "+content);
};
});
</script>
So my console log print as "success : undefined"
So content object not passing from directive scope to parentscope.
Please help me.
I believe your call inside template should either be:
<a href="#" ng-click="onItemClick({'content':content})">
or
<a href="#" ng-click="onItemClick({'obj':content})">
Can you try. For more details see this SO post Can an angular directive pass arguments to functions in expressions specified in the directive's attributes?