Controller $scope property does not change with expression in transcluded content - angularjs

The problem is that when you have transcluded content which contains an expression as an attribute of ng-click for example, the change does not occur in the scope of the parent controller as you can see in this fiddle:
http://jsfiddle.net/vt7rmqya/
Nothing happens when you click hide box inside of transcluded content.
<div ng-controller="myCtrl">
<panel ng-show='showBox'>
<div class='box'>
{{name}}
<br>
<button ng-click='showBox = false'>hide box button inside of transcluded content</button>
</div>
</panel>
<br>
Here, the expression in ng-click has no effect on the $scope.showBox in the controller but you would think that it would because the scope of the transcluded content should be the same as the controller scope, right?
BaseApp = angular.module('BaseApp', []);
BaseApp.controller('myCtrl', function($scope) {
$scope.name = 'bill jones';
$scope.showBox = true;
});
BaseApp.directive('panel', function() {
return {
restrict: 'E',
template: '<div>header<br><div ng-transclude></div></div>',
transclude: true,
}
})

I realized that the solution is simply to set the ng-click attribute to a function in the controller instead of an expression like so:
BaseApp.controller('myCtrl', function($scope) {
$scope.name = 'bill jones';
$scope.showBox = true;
$scope.hideBox = function() {
$scope.showBox = false;
}
});
<panel ng-show='showBox'>
<div class='box'>
{{name}}
<br>
<button ng-click='hideBox()'>hide box button inside of transcluded content</button>
</div>
</panel>
Here's the working fiddle:
http://jsfiddle.net/75f2hgph/

Related

Object not assignable in directive

In this plunk I have a directive that wraps a div. The div is shown when an ng-if condition is true (set with the click of a button).
The directive has a scope element css that is an object, where the object has an attribute width. Problem is that Angular complains when the directive is shown; see in the console the following error message when the button is clicked:
Expression '{ width: width}' in attribute 'css' used with directive
'modal' is non-assignable!
Note that this problem goes away when the $timeout in the directive is removed, but I cannot discard it.
Why does this happen and how to fix it (keeping the $timeout)?
HTML
<button ng-click="open()">Open modal</button>
<div modal ng-if="showModal" css="{ width: width}">
<p>some text in modal</p>
</div>
Javascript
angular.module("app", [])
.controller('ctl', function($scope) {
$scope.width = '200px';
$scope.open = function(){
$scope.showModal = true;
};
})
.directive("modal", function($timeout) {
var directive = {};
directive.restrict = 'EA';
directive.scope = { css: '=' };
directive.templateUrl = "modal.html";
directive.link = function (scope, element, attrs) {
$timeout(function(){
scope.css.height = '100%';
},100);
};
return directive;
});
Template
<style>
#modaldiv{
border:2px solid red;
}
</style>
<div id="modaldiv" ng-style="{'width': css.width,'height': css.height}">
Some content
</div>
The error appears since you are not passing a scope variable to your css attribute.
You can fix this by creating a variable that holds your css in ctrl and pass this variable to the css attribute.
Controller
$scope.css = {width: $scope.width};
HTML
<div modal ng-if="showModal" css="css">
<p>some text in modal</p>
</div>
Or alternatively create a local deep copy of css in the directive and manipulate the copy in your $timeout.
Directive
directive.link = function (scope, element, attrs) {
scope.cssCopy = angular.copy(scope.css);
$timeout(function(){
scope.cssCopy.width = '100%';
}, 100);
};
Template
<div id="modaldiv" ng-style="{'width': cssCopy.width,'height': cssCopy.height}">
Some content
</div>

Why my angular directive doesn't work?

I have a directive that is triggered when clicking on a button. The function inside the directive simply has to change the property value of the field. So what I try to do is to change from 'popover-trigger="blur"' to 'popover-trigger="none"'.
Here is my plunkr: http://plnkr.co/edit/L81fQgi7j1dEtf1QAZJ2?p=preview
or the code is here:
var app = angular.module('ui.bootstrap.demo', ['ngAnimate', 'ui.bootstrap']);
app.controller('PopoverDemoCtrl', function ($scope) {
$scope.dynamicPopover = {
content: 'Hello, World!',
templateUrl: 'myPopoverTemplate.html',
title: 'Title'
};
$scope.label = "Please click";
$scope.message = "ON FOCUS trigger a tooltip";
$scope.htmlPopover = "myPopoverTemplate.html";
});
app.directive("changeTrigger", function($compile){
return{
restrict: 'A',
link: function(scope, elm, attrs)
{
elm.bind('click', function(){
var t = document.getElementsByClassName('f')[0].setAttribute('popover-trigger', 'none');
$compile(t);
console.log("Click works");
});
}
}
});
html
<div ng-controller="PopoverDemoCtrl">
<br><br><br>
<p>{{message}}</p>
<input class="f" type="text" value="Click me!" uib-popover-template="htmlPopover" popover-trigger="focus" popover-popup-close-delay="1000" popover-placement="right" required>
<test-directive></test-directive>
<script type="text/ng-template" id="myPopoverTemplate.html">
<div>
<p>Click the button to stop triggering tooltip!</p>
<button change-trigger><b style="color: red">Stop tooltip</b></button>
<div class="label label-success">page</div>
</div>
</script>
</div>
You can't reconfigure the angular-bootstrap Popup element by changing uib-popup-* parameters; but you can bind a scope variable to popup-enable attribute to be able to switch the popup on/off. Add:
<input ... uib-popover-template="htmlPopover" popover-enable="enable" ...>
and
$scope.enable = true;
The problem here is that your button and the input box have different scopes. But you can fix this by retrieving the scope of the field:
var t = document.getElementsByClassName('f')[0];
var scope_ = angular.element(t).scope();
Of course, you need to use $scope.$apply for the scope to correctly handle two-way data binding:
scope_.$apply(function () {
scope_.enable = false;
});
Working Plunkr.

Angularjs change view template after link clicked

I am trying to change html template after link is clicked. Value is boolean, initial value is true and appropriate template is loaded, but when value changed to false new template is not loaded, I don't know the reason. When initial value of boolean is true other template is loaded successfully, but on method called not. Please, help.
Here is my code:
TaskCtrl
app.controller('TasksCtrl', ['$scope', 'TaskService', function ($scope, TaskService) {
// initialize function
var that = this;
that.newTask = true;
that.name = "My name is Nedim";
that.templates = {
new: "views/task/addTask.html",
view: "views/task/viewTask.html"
};
// load all available tasks
TaskService.loadAllTasks().then(function (data) {
that.items = data.tasks;
});
$scope.$on('newTaskAdded', function(event, data){
that.items.concat(data.data);
});
that.changeTaskView = function(){
that.newTask = false;
console.log("New task value: " + that.newTask);
};
return $scope.TasksCtrl = this;
}]);
task.html
<!-- Directive showing list of available tasks -->
<div class="container-fluid">
<div class="row">
<div class="col-sm-6">
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView()"></entity-task-list>
</div>
<div class="col-sm-6" ng-controller="TaskDetailCtrl as taskDetailCtrl">
<!-- form for adding new task -->
<div ng-if="taskCtrl.newTask" ng-include="taskCtrl.templates.new"></div>
<!-- container for displaying existing tasks -->
<div ng-if="!taskCtrl.newTask" ng-include="taskCtrl.templates.view"></div>
</div>
</div>
entityList directive
app.directive('entityTaskList', function () {
return {
restrict: 'E',
templateUrl: 'views/task/taskList.html',
scope: {
items: '='
},
bindToController: true,
controller: 'TasksCtrl as taskCtrl',
link: function (scope, element, attrs) {
}
};
});
directive template
<ul class="list-group">
<li ng-repeat="item in taskCtrl.items" class="list-group-item">
<a ng-click="taskCtrl.changeTaskView()">
<span class="glyphicon glyphicon-list-alt" aria-hidden="true"> </span>
<span>{{item.name}}</span>
<span class="task-description">{{item.description}}</span>
</a>
</li>
{{taskCtrl.newTask}}
Without any plunker or JSFiddle I can't tell for sure, but it might be issue with ng-if. I'm thinking of two workarounds.
First that I think is better. Use only 1 ng-include and only change the template.
HTML:
<entity-task-list items="taskCtrl.items" openItem="taskCtrl.changeTaskView('view')"></entity-task-list>
...
<div ng-include="taskCtrl.currentTemplate"></div>
JS:
that.currentTemplate = that.templates.new;
...
that.changeTaskView = function(template) {
that.currentTemplate = that.templates[template];
};
Or if you don't like this solution, try with ng-show instead of ng-if. With ng-show the elements will be rendered with display: none; property when the page loads, while with ng-if they will be rendered when the passed value is true.
Hope this helps you.

How to pass the value to the local scope of the directive?

I'm learning directives, it's cool thing but sometimes a little complicated. Please can somebody explain this:
I have custom directive with template of little form and it own local scope, and want to change the list of items form the main controller.
Please see it:
By clicking on change button I open a custom directive with input form template
<body ng-controller="testCtrl">
<h1>Hello Plunker!</h1>
<ul>
<li ng-repeat="item in list">
<div> {{item}} </div>
<button ng-click="edit()">Change</button>
<change ng-if='editable'></change>
</li>
</ul>
</body>
"Change" is the custom directive with the input form inside the other Html file
.directive('change', function(){
return {
restrict: "E",
replace: true,
scope: {
show: '='
},
templateUrl: "other.html"
}
})
Also there is another directive inside "change" directive. It's a button which I want to use inside "change" directive and inside my main controller. I can see my item list only from scope.$parent.item, but how to pass it in the function of my button directive?
How can I implement this?
.directive('save', function(){
return {
restrict: "E",
replace: true,
template: ' <button class="btn btn-sm btn-warning" ng-click="saving(item)">SAVE</button>',
link: function(scope,element,attr){
scope.saving = function(item){
console.log(item);
console.log(scope.$parent.item)
}
}
}
})
Please see the example: Plnkr
P.S. Sorry for my explanation, I hope that everything is clear
Simply pass in the item to each of your directives that need access to it. For example:
<li ng-repeat="item in list">
//snip
<save item="item"></save>
//snip
</li>
And then define your directive to bind the attribute to the scope:
.directive('save', function(){
return {
//snip
scope: {
item: '=' //two-way binding to 'scope.item'
},
//snip
link: function(scope, element, attr){
scope.saving = function() {
console.log(scope.item);
}
};
});
In angularjs, you have the $emit event.
Dispatches an event name upwards through the scope hierarchy notifying the registered $rootScope.Scope listeners.
$rootScope.Scope
HTML
<body ng-controller="testCtrl">
<h1>Hello Plunker!</h1>
<ul>
<li ng-repeat="item in list">
<div> {{item}} - <input type="text" ng-model="item">
<button ng-click="edit()">Change</button>
</div>
<div>
<change ng-if='editable'></change>
</div>
</li>
</ul>
</body>
Directive
directive('save', function(){
return {
restrict: "E",
replace: true,
template: ' <button class="btn btn-sm btn-warning" ng-click="saving(item, $parent.$index)">SAVE</button>',
link: function(scope,element,attr, controller){
scope.saving = function(item, index){
//Build our object with the index of $scope.list which is updated & the item value
var obj = {
index: index,
item: item
};
//Emit a 'change' event, and we pass our object data
scope.$emit('change', obj)
}
}
}
})
In the "change" directive, we use $emit to pass event, and to notify our $rootScope.Scope.
In the "change" directive template, you can see that we pass the $parent.$index and not the $index, in order to get the current item of the list.
Controller
controller('testCtrl', function($scope){
$scope.list = [1,2,3,4,5,6,7,8,9];
//Listen for 'change' event
$scope.$on('change', function(event,value){
//Set to the list value.index our value.item
$scope.list[value.index] = value.item;
});
$scope.editable = false;
$scope.edit = function(){
$scope.editable = !$scope.editable;
}
})

Angular directive to append or delete a input element with unique id based on button click

I'm new to Angular and am trying to do this the Angular Way. I have a list with several <li> elements created using ng-repeat. In each <li> I have a button that increments a counter and one that decrements the counter and initially one <input type=text>. I want to append or remove a duplicate <input type=text> with a unique id corresponding to whether the increment or decrement button is clicked.
Here is the markup for the portion of my page template I'm working with
<ul class="list">
<li class="item" ng-repeat="resource in resources">
<div class="resource-desc">{{resource.description}}</div>
<img ng-src="{{resource.thumb}}">
<div class="incrementer" ng-controller="CounterCtrl">
<button id='{{$index}}' class="count-btn ion-arrow-up-b" ng-click="increment()" ng-init="count=0">
<span class="ion-plus-round"></span>
</button>
<div id='{{$index}}'class="count-btn count">{{count}}</div>
<button id='{{$index}}' class="ion-arrow-down-b" ng-click="decrement()">
<span class="ion-minus-round"></span>
</button>
</div>
<div id='{{$index}}' class="multi-name-input">
<div class="input_wrapper">
<input type="text" name="{{$index}}resource" required>
<label for="{{$index}}resource">Enter a Name to Display(ie Bay 1)</label>
</div>
</div>
</li>
</ul>
And my Counter controller
'use strict';
angular.module('myapp.controllers', ['ionic', 'ui.bootstrap'])
.controller('CounterCtrl', function($scope) {
$scope.decrement = function() {
$scope.count = $scope.count - 1;
if ($scope.count < 0){
$scope.count = 0;
}
};
$scope.increment = function() {
$scope.count = $scope.count + 1;
};
})
The input markup for the partial I want appended
<div class="input_wrapper">
<input id='{{$index}}' type="text" name="resource{{$index}}" required>
<label for="resource{{$index}}">Enter a Name to Display</label>
</div>
I think my directive should look something like this
.directive('bw-input-append',function($compile){
return {
templateUrl: '../../templates/partials/text_input.html',
transclude: true
link: function(scope, element){
element.click(function(){
element.parent().find('.multi-name-input').append($compile(template)(scope));
});
}
};
});
But I'm really not sure if that's right at all or what options I might need or want in the directive.
I was able to figure out how to write directives for both appending and removing input elements and attached those to their respective increment and decrement buttons on the counter.
Here is the markup for the buttons
<section ng-app="myApp" ng-controller="MainCtrl">
<addinputsbutton></addinputsbutton>
<removeinputsbutton></removeinputsbutton>
<div id="space-for-inputs"></div>
</section>
..and here are the directives.
var myApp = angular.module('myApp', []);
function MainCtrl($scope) {
$scope.count = 0;
}
//Directive that returns an element which adds inputs on click
myApp.directive("addinputsbutton", function(){
return {
restrict: "E",
template: "<button id='{{$index}}' class=\"count-btn ion-arrow-up-b\" ng-click=\"increment()\" ng-init=\"count=0\" addinputs><span class=\"ion-plus-round\">+</span></button>"
}
});
//Directive for adding inputs on click
myApp.directive("addinputs", function($compile){
return function(scope, element, attrs){
element.bind("click", function(){
angular.element(document.getElementById('space-for-inputs')).append($compile("<div class='resource-input'><input id='{{$index}}' type=\"text\" name=\"resource{{$index}}\" required><label for=\"resource{{$index}}\">Enter a Name to Display</label></div>")(scope));
});
};
});
//_______________________________________________________________________
//Directive that returns an element which removes inputs on click
myApp.directive("removeinputsbutton", function(){
return {
restrict: "E",
template: "<button id='{{$index}}' class=\"count-btn ion-arrow-down-b\" ng-click=\"decrement()\" ng-init=\"count=0\" removeinputs><span class=\"ion-minus-round\">-</span></button>"
}
});
//Directive for removing inputs on click
myApp.directive('removeinputs', function(){
return function(scope, element){
element.bind('click', function(){
document.getElementById('space-for-inputs').removeChild(document.getElementById('space-for-inputs').lastChild);
});
};
});
For some reason I couldn't get the 'addinput' and 'removeinput' directives to work in JSFiddle unless I added them to a custom button created from the element directives. But the 'addinput' and 'removeinput' elements worked fine in plain button tags in my application. Here is the JSFiddle in case any one is interested.

Resources