Compiled dynamic template can't access ng-controller functions - angularjs

I have the following code:
dynamicTemplateItem.js:
angular.module('mod1')
.directive('dynamicTemplateItem', ['$rootScope', '$compile', '$parse', '$http',
function ($rootScope, $compile, $parse, $http) {
var linker = function ($scope, $element, $attrs) {
var templateUrl = $rootScope.dynTemplate[$attrs.type];
// it will be something like "views/templates/template.html"
if (templateUrl) {
$http.get(templateUrl).then(function (response) {
$compile($element.html(response.data).contents())($scope);
}, function (e) {
console.error(e);
});
}
};
return {
restrict: "E",
link: linker,
scope: {
type: '='
}
};
}]);
views/main html:
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>Test</title>
</head>
<body>
<div ng-cloak class="container">
<dynamic-template-item type="login"/>
</div>
</body>
</html>
views/templates/template.html:
<div ng-controller="Controller1">
<div> ctrl1 </div>
<div ng-controller="Controller2">
<div> ctrl2 </div>
<select ng-change="switch()" ng-options="test.name for test in tests.availableTests track by test.id"
ng-model="tests.selectedTest"></select>
</div>
</div>
Controller2.js:
angular.module("mod1").controller("Controller2", ['$scope', '$rootScope', '$location', "$state"
function ($scope, $rootScope, $location, $state) {
// ...
$scope.switch = function () {
// ...
};
}]);
The problem is that $scope.switch function is not accessed and also any other function in Controller2.
What am I doing wrong?
Is was used to work before introducing dynamic templating with the dynamicTemplateItem directive, so before dividing main html and template.html, when I was using templateUrl: views/main.html in directive.
My angularjs version is 1.7
Thanks

The problem here is that you are declaring the directive with an isolated scope:
scope: {
type: '='
}
Since you don't seem to require it as you are retrieving the type from the attributed, you should remove the scope from de declaration and your directive should be able to access the parent scope.
return {
restrict: "E",
link: linker
};

Related

Access the main controller's scope from inside a directive

Suppose the following blueprint code:
<div ng-controller="myCtrl">
<div ng-repeat="...">
<div ng-repeat="...">
<div ng-repeat="...">
<div ng=if="..." my-directive>
</div>
</div>
</div>
</div>
</div>
myApp.directive('myDirective', function() {
return {
controller: function($scope){
console.log('controller scope');
console.log($scope);
},
link:function(scope,element){
console.log('link scope');
console.log(scope);
}
}
});
Both outputs in console will point to the scope created by ng-if directive. My question is how may I access myCtrl's scope from inside the directive . Of course not by using $parent.$parent....
The easiest way could be by using require in the directive, like:
<div ng-controller="MyCtrl">
<div my-directive></div>
</div>
var myApp = angular.module("app", []);
myApp.controller("MyCtrl", function($scope) {
this.text = "I am in Controller Scope";
this.getValue = function() { return this.text; };
});
myApp.directive("myDirective", function() {
return {
require: "^ngController",
link: function(scope, elem, attrs, ngCtrl) {
elem.text(ngCtrl.getValue());
}
};
});
EDIT
In your case, I think you could use the controller scope variables and methods in the directive by using scope binding with &; snippet below:
<div ng-controller="MyCtrl as vm">
<my-directive on-get-value="vm.getValue()">
</my-directive>
</div>
angular.module('app', [])
.controller('MyCtrl', function($window) {
var vm = this;
vm.getValue = function() { $window.alert("I am in Controller Scope"); };
})
.directive('myDirective', function() {
return {
scope: {
onGetValue:'&'
},
controllerAs:'vm',
controller: function($scope) {
$scope.onGetValue();
}
};
});
Use services to share data between angular components. This question might be a good start: Share data between AngularJS controllers. This approach will work for sharing data between controller and directive as well
When you are creating your directive, the returning function is called DDO (Directive Defining Object). One of its attributes is 'scope'. if you initialize it with scope : true, the directive will prototypically inherit the parent scope. If you set scope: false, the directive will use the parent scope. And finally, if you set scope : {...}, it will created an isolated scope.
var app = angular.module("test",[]);
app.controller("myCntrl",function($scope){
$scope.text = "Im in controller Scope";
});
app.directive("myDirective", function(){
return {
restrict: "EA",
scope: true,
template: "<div>Where are you, directive ? {{text}}</div>"
};
});
h2 {
cursor: pointer;
}
.directive {
border: 5px solid #F5BF6E;
padding: 10px;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.22/angular.min.js"></script>
<div ng-app="test">
<div ng-controller="myCntrl">
<h2 ng-click="reverseName()">Where are you ? {{text}}</h2>
<div my-directive class='directive'></div>
</div>
</div>
You can check this link for more details : Directive Scopes

Angularjs - Scope value is not applied in template

I have used the directive scope in directive template.
I have tried to get the html from template cache which was stored earlier.
But the current directive scope is not applied to the directive. I don't what will be the reason.
I have tried to compile the template and get the value. But not applied.
contentString = $templateCache.get('template/MyTemplate')
var div = document.createElement("div");
div = angular.element(div).html(contentString);
var s = $compile(div.contents())($scope);
template/MyTemplate would be following
<div>
{{obj.value}}
</div>
Directive scope like following,
link: function ($scope, $element, $attributes) {
$scope.obj.value="This is my test"
}
I got the output like
<div class="ng-scope">
{{obj.value}}
</div>
What will be the issue?
Check this example which is using a custom directive with an isolated scope. I hope the below examples will be of help to you.
angular
.module('demo', [])
.directive('hello', hello);
hello.$inject = ['$templateCache', '$compile'];
function hello($templateCache, $compile) {
var directive = {
scope: {
},
link: linkFunc
};
return directive;
function linkFunc(scope, element, attrs, ngModelCtrl) {
scope.obj = {
value: 'Hello, World!'
};
var template = $templateCache.get('templateId.html');
element.html(template);
$compile(element.contents())(scope);
}
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="demo">
<hello></hello>
<script type="text/ng-template" id="templateId.html">
<div>
{{obj.value}}
</div>
</script>
</div>
Another example using controller aliasing syntax i.e. controller as with a directive to be consistent with using controller as with view and controller pairings
angular
.module('demo', [])
.controller('DefaultController', DefaultController)
.directive('hello', hello);
function DefaultController() {
var vm = this;
vm.message = 'Hello, World!';
}
hello.$inject = ['$templateCache', '$compile'];
function hello($templateCache, $compile) {
var directive = {
link: linkFunc,
scope: {
message: '='
},
controller: HelloController,
controllerAs: 'vm',
bindToController: true
};
return directive;
function linkFunc(scope, element, attrs, ngModelCtrl) {
var template = $templateCache.get('templateId.html');
element.html(template);
$compile(element.contents())(scope);
}
}
function HelloController() {
var vm = this;
}
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.5.8/angular.min.js"></script>
<div ng-app="demo">
<div ng-controller="DefaultController as ctrl">
<hello message="ctrl.message"></hello>
<script type="text/ng-template" id="templateId.html">
<p>{{vm.message}}</p>
</script>
</div>
</div>

How to update a model of directive 1 from directive 2 and see changes on view?

I have two directives and i want to update model (this in controllerAs syntax) in directive 1.
// Linkbox
SApp.directive('linkBox', ['$rootScope', '$compile', '$templateCache', '$state', '$timeout', function($rootScope, $compile, $templateCache, $state, $timeout){
return {
controller: function($scope, $element, $attrs, $transclude) {
var vm = this;
vm.setBoxTitle = function(title){
vm.boxTitle = title;
console.log('setBoxTitle', title); // returns correct value!
// also tried this:
// $timeout(function(){
// vm.boxTitle = title;
// console.log('title of box', vm.boxTitle);
// }, 1);
}
},
restrict: 'A',
templateUrl: 'linkbox.html',
controllerAs: 'lbCtrl',
link: function($scope, iElm, iAttrs, controller) {
console.log('link');
}
};
}]);
// SubCategories linkbox
SApp.directive('subCategories', ['$rootScope', '$compile', '$templateCache', '$state', function($rootScope, $compile, $templateCache, $state){
return {
require: 'linkBox',
restrict: 'A',
link: function($scope, iElm, iAttrs, lbCtrl) {
lbCtrl.setBoxTitle('Title of box...');
}
};
}]);
Using directives:
<div link-box sub-categories ></div>
This is the view (linkbox.html)
<div class="linkbox">
<h3 class="title" ng-bind="lbCtrl.boxTitle"></h3>
<ul> <li> <a>...</a> </li> </ul>
</div>
Communication of directives is ok and console.log returns correct value, But I see no change in view. Can you help please?
Update:
When i manually set controller.boxTitle = 'A title' in link function of linkBox directive, View has updated value. So there is no problem in html code.
Problem solved.
There was another usage of link-box directive like this:
<div link-box sub-categories ></div>
<div class="area" link-box related-searchs ></div>
In second use of link-box, I just added another directive without setting title. I should use isolate scope to prevent colision between models of different modules.
As per code in the jsbin , ng-template has been used , if this is the case then you have provide an id or class for the template as below id="myTpl", and include that using ng-include directive
<script type="text/ng-template" id="myTpl">
<div class="box linkbox" >
<div class="inr">
<div class="content">
<h3 class="title" ng-bind="lbCtrl.boxTitle"></h3>
<ul>
<li><a>link 1</a></li>
<li><a>link 2</a></li>
</ul>
</div>
</div>
</div>
</script>
<div link-box sub-categories ng-include="'myTpl'"></div>
http://jsbin.com/baneyezezu/1/edit?html,js,output

Compile a dynamic directive inside a template

I have a directive like this:
foldeskApp.directive('contributionFooter', function() {
return {
restrict: 'C',
template: '<button type="button" class="btn" ng-class="{\'btn-success\': canCreate()}">Add</button>'
};
});
And a controller like this:
foldeskApp.controller('MainCtrl',
['Auth', '$scope', function(Auth, $scope) {
$scope.footerType = 'contribution';
}]);
Can I call the directive like this?
<div class="modal-footer {{footerType}}-footer"></div>
You need to compile the DOM using the $compile service.
Here's an example of how to achieve that, although I'm not a fan of using $timeout here:
http://codepen.io/jlowcs/pen/jEKKjZ
HTML:
<div ng-controller="MainCtrl">
<div class="modal-footer {{footerType}}-footer"></div>
</div>
JS:
angular.module('exampleApp', [])
.directive('contributionFooter', function() {
return {
restrict: 'C',
template: '<button type="button" class="btn" ng-class="{\'btn-success\': canCreate()}">Add</button>'
};
})
.controller('MainCtrl', function($scope, $element, $timeout, $compile) {
$scope.footerType = 'contribution';
//timeout to do it when {{footerType}} has been replaced
//but it would probably be best to do this in a link function in a directive
$timeout(function () {
$compile($element.children())($scope);
});
});
angular.bootstrap(document, ['exampleApp']);

Scope issue in AngularJS using AngularUI Bootstrap Modal

plunker: http://plnkr.co/edit/wURNg8ByPYbEuQSL4xwg
example.js:
angular.module('plunker', ['ui.bootstrap']);
var ModalDemoCtrl = function ($scope, $modal) {
$scope.open = function () {
var modalInstance = $modal.open({
templateUrl: 'modal.html',
controller: 'ModalInstanceCtrl'
});
};
};
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.ok = function () {
alert($scope.text);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
index.html:
<!doctype html>
<html ng-app="plunker">
<head>
<script src="http://ajax.googleapis.com/ajax/libs/angularjs/1.0.8/angular.js"></script>
<script src="http://angular-ui.github.io/bootstrap/ui-bootstrap-tpls-0.6.0.js"></script>
<script src="example.js"></script>
<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
</head>
<body>
<div ng-controller="ModalDemoCtrl">
<button class="btn" ng-click="open()">Open me!</button>
<div ng-show="selected">Selection from a modal: {{ selected }}</div>
</div>
</body>
</html>
modal.html:
<div class="modal-header">
<h3>I'm a modal!</h3>
</div>
<textarea ng-model="text"></textarea>
<div class="modal-footer">
<button class="btn btn-primary" ng-click="ok()">OK</button>
<button class="btn btn-warning" ng-click="cancel()">Cancel</button>
</div>
Why I can't get the $scope.text defined in ModalInstanceCtrl, even though I can use $scope.ok and $scope.cancel?
Looks like a scope issue. I got it to work like this:
var ModalInstanceCtrl = function ($scope, $modalInstance) {
$scope.input = {};
$scope.ok = function () {
alert($scope.input.abc);
};
$scope.cancel = function () {
$modalInstance.dismiss('cancel');
};
};
HTML:
<textarea ng-model="input.abc"></textarea>
Update Nov 2014: the issue is fixed with angular-ui-bootstrap 0.12.0 - the transclusion scope is merged with the controller's scope. There is no need to do anything. Just stay with:
<textarea ng-model="text"></textarea>
Before 0.12.0:
Angular-UI modals are using transclusion to attach modal content, which means any new scope entries made within modal are created in child scope.
You should use inheritance and initialize empty text entry in parent $scope
or you can explicitly attach the input to parent scope:
<textarea ng-model="$parent.text"></textarea>
Let'me try to explain the reason. ui-bootstrap modal sourcecode:
.directive('modalWindow', ['$modalStack', '$timeout', function ($modalStack, $timeout) {
return {
restrict: 'EA',
scope: {
index: '#',
animate: '='
},
replace: true,
transclude: true,
templateUrl: function(tElement, tAttrs) {
return tAttrs.templateUrl || 'template/modal/window.html';
},
and the template sourcecode - window.html:
<div tabindex="-1" role="dialog" class="modal fade" ng-class="{in: animate}" ng-style="{'z-index': 1050 + index*10, display: 'block'}" ng-click="close($event)">
<div class="modal-dialog" ng-class="{'modal-sm': size == 'sm', 'modal-lg': size == 'lg'}"><div class="modal-content" modal-transclude></div></div>
there is a directive modal-transclude,your dialog content will insert into it, it's sourcecode:
.directive('modalTransclude', function () {
return {
link: function($scope, $element, $attrs, controller, $transclude) {
$transclude($scope.$parent, function(clone) {
$element.empty();
$element.append(clone);
});
}
};
})
now take a look at offical doc of $compile:
Transclusion Functions
When a directive requests transclusion, the compiler extracts its contents and provides
a transclusion function to the directive's link function and controller.
This transclusion function is a special linking function that will return the compiled
contents linked to a **new transclusion scope.**
transclude will create a new scope of controller scope

Resources