Using bindToController without a template - angularjs

I'm trying to learn how isolated scope, bindToController and controllerAs work.
If I make a directive with an isolated scope and a template, it works as expected:
<!DOCTYPE html>
<html ng-app="app">
<head>
<style>
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
function directiveTest() {
function CtrlTest() {
this.foo = "honk";
this.clearFoo = function() {
this.foo='';
};
}
return {
restrict : 'E',
scope: {
label : '#',
lg : '#'
},
bindToController : true,
controller: CtrlTest,
controllerAs: 'ctrlTest',
template: '<label>\n {{ ctrlTest.label }}\n <input type="text" ng-model="ctrlTest.foo"/>\n</label>\n<button ng-click="ctrlTest.clearFoo()">Clear</button>\n\n<div>{{ ctrlTest.foo }}</div>\n\n<div ng-show="ctrlTest.foo.length> ctrlTest.lg">\n Long string !\n</div>\n'
};
}
var app = angular.module('app',[]);
app.directive('dirTest',[directiveTest]);
</script>
</head>
<body>
<dir-test label="Type something now:" lg="7">
</dir-test>
</body>
</html>
I was trying to do the same without the template, but I can't make it work:
<!DOCTYPE html>
<html ng-app="app">
<head>
<style>
</style>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.3.14/angular.min.js"></script>
<script>
function directiveTest() {
function CtrlTest() {
this.foo = "honk";
this.clearFoo = function() {
this.foo='';
};
}
return {
restrict : 'E',
scope: {
},
bindToController : true,
controller: CtrlTest,
controllerAs: 'ctrlTest'
};
}
var app = angular.module('app',[]);
app.directive('dirTest',[directiveTest]);
</script>
</head>
<body>
<dir-test>
<label>
Type something again :
<input type="text" ng-model="ctrlTest.foo"/>
</label>
<button ng-click="ctrlTest.clearFoo()">Clear</button>
<div>{{ ctrlTest.foo }}</div>
<div ng-show="ctrlTest.foo.length>7">
Long string !
</div>
</dir-test>
</body>
</html>
However that works if I set scope to true instead of an isolate scope.
Can someone explain to me how to make the 2nd example work, or if it's not possible, why ?

Flip the values of bindToController and scope around.
{
....
scope: true,
bindToController: {}
...
}
I have just hit the same issue over the weekend, and made a simple complete example here: bindToController Not Working? Here’s the right way to use it! (Angular 1.4+)

Related

angularjs - access transclude html scope from hosting directive

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

undefined ng-repeat variable when passed from directive template to a controller

I have a directive with a template that uses ng-repeat on an anchor tag creating a gallery of anchors. Each anchor also has an ng-click which when clicked calls a parent controller function. To this function is passed the ng-repeat item.
Problem : This item when accessed inside the parent controller method is undefined
Here is a test scenario to simulate the similar situation
<testdirective func="show(x)" items="buttons"></testdirective>
http://plnkr.co/edit/43aNqFS71Jn9vOdh6AG2?p=preview
There are two changes you need to make.
in your index.html make a refrence to your function:
<testdirective func="show" items="buttons"></testdirective>
and in your testdirective change your template like so:
template: '<button ng-repeat="item in items" ng-click="func()(item)" id="submit" />{{item}}</button>',
Notice the change in the ng-click - first brackets is to get a refrence to the function itself and the second is to invoke the function with the paramater.
I also made a fork of your plunker:
http://plnkr.co/edit/nECPbL8YoToi0jP9HJHQ?p=preview
Please tell me if that's what you wanted to achieve
Your directive should bind to a controller, change it as follows,
app.directive("testdirective", function() {
return {
restrict: "E",
controller: 'testController',
template: '<button ng-repeat="item in items" ng-click="show(item)" id="submit" />{{item}}</button>',
scope: {
items: "=",
func: '&'
}
};
});
DEMO
// Code goes here
var app = angular.module('test', []);
app.controller('testController', function($scope) {
$scope.buttons = ['b1', 'b2', 'b3', 'b4', 'b5'];
$scope.show = function(x) {
alert(x);
}
})
app.directive("testdirective", function() {
return {
restrict: "E",
controller: 'testController',
template: '<button ng-repeat="item in items" ng-click="show(item)" id="submit" />{{item}}</button>',
scope: {
items: "=",
func: '&'
}
};
});
<!DOCTYPE html>
<html ng-app='test'>
<head>
<script data-require="angular.js#*" data-semver="1.3.0-rc.1" src="https://code.angularjs.org/1.3.0-rc.1/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
</head>
<body>
<div ng-controller="testController">
<h1>Hello Plunker!</h1> {{ testValue }}
<testdirective func="show(x)" items="buttons"></testdirective>
</div>
</body>
</html>

Angular add dynamic html(directive) with object as parameter

Doesn't sound like it would be a big deal but I don't know what to google for to solve this. I want to be able to fetch an object with $http and then render that info with the help of a directive.
JS:
angular.module("test", []);
angular.module("test").directive('myTest', function() {
return {
templateUrl: 'myTest.html'
};
});
angular.module("test").controller("myCtrl", function($http, $compile){
var vm = this;
vm.name = "Viktor";
vm.country = "Sweden";
vm.origin = "controller";
vm.click = function(){
$http.get("data"+Math.floor((Math.random() * 2) + 1)+".json").success(function(data){
$("body").append($compile("<my-test></my-test>")(data));
})
}
})
Template:
<div>
<div>My name is: {{vm.name}}</div>
<div>I live in : {{vm.country}}</div>
<div>Source origin : {{vm.origin}}</div>
</div>
index.html
<!DOCTYPE html>
<html ng-app="test">
<head>
<script data-require="jquery#*" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script data-require="angular.js#*" data-semver="1.4.7" src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.7/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body ng-controller="myCtrl as vm">
<button ng-click="vm.click()">Add panel</button>
<my-test></my-test>
</body>
</html>
Plunker: http://embed.plnkr.co/YcG9ZFxuR3PYBYASjzm2/preview
I just can't edit your plnkr file.
But bindToController should help you. It should be somethind like this, I create a controller for the directive :
angular.module("test").directive('myTest', function() {
return {
restrict: 'E',
scope: {},
templateUrl: 'myTest.html',
controller: function(){},
controllerAs: 'ctrl',
bindToController: {
name: '=',
country: '=',
origin: '='}
}
});
Also I change the alias of the controller in the template :
<div>
<div>My name is: {{ctrl.name}}</div>
<div>I live in : {{ctrl.country}}</div>
<div>Source origin : {{ctrl.origin}}</div>
<input ng-model="ctrl.name"/>
</div>
Here is a working example modified on your plunkr : plunkr.
I am still stuck in the jQuery way of thinking, I guess I found out how to do it the Angular way. Just to add the newly fetched item to a list and then render the list. Not what I was originally aiming for but that is probably the prettiest way.
And if one now HAVE to add new items instead of redrawing the whole list, I guess one could send in the object as a json-string or set scope variables on the directive for each parameter in the object.
Plunker: http://plnkr.co/edit/5ElTIx?p=preview
Html:
<body ng-controller="myCtrl as vm">
<button ng-click="vm.click()">Add panel</button>
<my-test ng-repeat="panel in vm.panels" obj="panel"></my-test>
</body>
Js:
angular.module("test").controller("myCtrl", function($http, $compile){
var vm = this;
vm.name = "Viktor";
vm.country = "Sweden";
vm.origin = "controller";
vm.panels = [{
name:"Viktor2",
origin:"Controller array",
country:"Sweden"
}];
vm.click = function(){
$http.get("data"+Math.floor((Math.random() * 2) + 1)+".json").success(function(data){
vm.panels.push({
name:data.name,
origin:data.origin,
country:data.country
})
})
}
})

TwoWay data binding in AngularJS custom directive not working in ng-show

My problem is that $scope.isValid is not binded to ng-show="{{isValid}}" and I don't know why.
My goal is when isValid is true this span should be visible and it is valid when there is a value in input field.
<span class="warning" ng-show="{{isValid}}" style="color: red;">
Please answer the question to continue.
</span>
Here is plunker link to my scenario.
http://plnkr.co/edit/qZ47TFg2G741PxdARiYR?p=preview
try to use like this :
'use strict';
var codeArtApp = angular.module("codeArtApp", ['ui.router']);
codeArtApp.directive('submitquestion', function($http) {
return {
scope: {
questId: '#',
answerId: '#',
isValid: '=ngShow',
},
link: function(scope, el, attrs) {
$(el).find('.btn').on('click', function(e) {
scope.sendQuesiton();
})
},
controller: function($scope) {
$scope.isValid = false;
$scope.sendQuesiton = function () {
$scope.validateQuestion();
if ($scope.isValid) {
alert('is Valid')
} else {
alert('Not Valid');
}
}
$scope.validateQuestion = function() {
if ($scope.answerId.length) {
console.log($scope.answerId);
$scope.isValid = true;
}
}
}
};
});
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.2.23/angular.min.js"></script>
<!DOCTYPE html>
<html ng-app="codeArtApp">
<head>
<script data-require="angular.js#*" data-semver="1.4.3" src="https://code.angularjs.org/1.4.3/angular.js"></script>
<script data-require="ui-router#*" data-semver="0.2.8" src="http://angular-ui.github.io/ui-router/release/angular-ui-router.js"></script>
<script data-require="jquery#2.1.4" data-semver="2.1.4" src="http://code.jquery.com/jquery-2.1.4.min.js"></script>
<script src="script.js"></script>
</head>
<body>
<input type="text" ng-model="edit">
<div data-submitquestion="" data-questId="862" data-answer_id="{{edit}}">
<button class="btn btn__next">Следващ въпрос</button>
<span class="warning ng-hide" ng-show="isValid == true" style="color: red;">
Please answer the question to continue.
</span>
</div>
</body>
</html>
Please do ng-show="isValid"
You don't need the curly braces because ng-show is a directive it will invoke $parse for you.
I found that I have several problems. One is related to scope binding camelcase name. Also what you said about curly brackets. and missing binding data attribute isvalid="isvalid"in directive main tag.
Similar to this problem.
Angular "=" scope does not work with camelCase
Here is my working scenario in Plunker
it should be isvalid: '=', insted of isValid: '=',
I was missing isvalid="isvalid" in directive tag :
and no curly brackets here ng-hide="isvalid"
Working solution:
http://plnkr.co/edit/puOeoJ37LZXZ7ZRXVTKt?p=preview

AngularJS - Create a directive that adds a sibling element

I'm creating a my-validate directive that looks something like this
<input my-validate="customValidation" ng-model="model" />
What I want to do is to attach a sybling element to the directive like this
Error template:
<ul class"errors">
<li ng-repeat="for error in errors">{{error}} not valid</li>
</ul>
errors is defined in the scope of the directive.
I've added the error template in the compile function, but the problem I have is that the scope in the link function is not the same as the attached template.
Here is a plunker to illustrate the issue: http://plnkr.co/edit/ghdtdYruQaaO0Yxxlrt1?p=preview
'world' is seen in the directive template, but not on the added element :S.
That's because your div "2 hello" is outside the container where your scope is visible.
you can use element.append() instead of element.after() to have the scope available.
Directive
var app = angular.module('plunker', []);
app.directive('myValidate', function($compile) {
return {
template: '<span>1. Hello {{world}} my scope is {{$id}} (parent: {{$parent.$id}})<span/>',
replace: true,
restrict: 'A',
scope: true,
compile: function (element) {
element.append('<div>2. Hello {{ world }}, my scope is {{$id}} (parent: {{$parent.$id}})</div>');
return function(scope) {
scope.world = 'World';
//$compile()(scope);
};
}
};
});
HTML
<!DOCTYPE html>
<html ng-app="plunker">
<head>
<meta charset="utf-8" />
<title>AngularJS Plunker</title>
<script data-require="angular.js#1.1.5" data-semver="1.1.5" src="http://code.angularjs.org/1.1.5/angular.min.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="app.js"></script>
</head>
<body>
<input my-validate="" />
</body>
</html>
http://plnkr.co/edit/dU3holBCePKe0ZAwQKh1?p=preview
I was reading and checking the example because I was in the same situation to display validation messages but under the input field and the message can change according to what kind of validation is required.
So I came up with this solution
var app = angular.module('app', []);
app.controller('ctrl', function($scope, CONSTANTS) {
$scope.title = "title";
$scope.CONSTANTS = CONSTANTS;
});
app.constant('CONSTANTS', {
LENGHT_1: 3,
LENGHT_2: 4
});
app.directive('dir', function($compile) {
return {
scope: true,
restrict: 'A',
require: '?ngModel',
link: function(scope, elem, attrs, ngModel) {
scope.maxLength = false;
scope.required = false;
scope.max = scope.$eval(attrs['ngMaxlength']);
var tpl = '<div ng-if="maxLength" ng-include="\'length.tpl.html\'"></div>' +
'<div ng-if="required" ng-include="\'required.tpl.html\'"></div>';
var el = $compile(tpl)(scope);
elem.after(el);
scope.$watch(attrs['ngModel'], function(newValue, oldValue, scope) {
if (ngModel.$error !== null && ngModel.$error.maxlength) {
scope.maxLength = true;
} else {
scope.maxLength = false;
}
if (ngModel.$error !== null && ngModel.$error.required && ngModel.$dirty) {
scope.required = true;
} else {
scope.required = false;
}
});
}
}
});
<!DOCTYPE html>
<html ng-app="app">
<head>
<script data-require="angular.js#1.4.7" data-semver="1.4.7" src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.7/angular.js"></script>
<link rel="stylesheet" href="style.css" />
<script src="script.js"></script>
<script type="text/ng-template" id="length.tpl.html">
max length {{max}}
</script>
<script type="text/ng-template" id="required.tpl.html">
required
</script>
</head>
<body ng-controller="ctrl">
<h1>Input Validation</h1> {{title}}
<br><br>
<form name="form" novalidate>
<input dir name="input_one" ng-model="bar" ng-maxlength="CONSTANTS.LENGHT_1" required>
<br>
input one: {{form.input_one.$error}}
<br>
<br>
<input dir name="input_two" ng-model="foo" ng-maxlength="CONSTANTS.LENGHT_2">
</form>
<br>
input two: {{form.input_two.$error}}
</body>
</html>
On Plunkr
Hope it helps.
I think you're on the right track by using the form errors to toggle display. That's exactly how it's recommended in the standard Angular documentation.
If you'd like to show multiple errors for a single input, however, and possible even control the error messages from there, I'd recommend utilizing a service, such as implemented at http://plnkr.co/edit/iNcNs2ErrOnYf9I7whdu?p=preview.
Right now you can have one message per token, but as many tokens as you want per input. If you want multiple messages per token, just use an array of messages instead of single string value (note, unset does become more complicated with that method).
Hope that helps,
Alex

Resources