Directive two way binding watch property - angularjs

I'm trying to use directives in my angularjs app, it's the first i'm trying to apply, so i'm not sure if its right.
The thing is that i want to wrap the ui-select directive into another directive and then i want to watch the selec if a new value has been selected. I'm able to populate the select but i don't know why it doesn't trigger the watch... here is the controller:
.controller('IngredientesDatosGeneralesController' ,['$scope', 'PrivateAlergenosUtilsService',
'PrivateRestauranteService', 'PrivateIngredienteService',
function($scope, PrivateAlergenosUtilsService, PrivateRestauranteService,
PrivateIngredienteService){
var _this = this;
_this.PrivateIngredienteService = PrivateIngredienteService;
_this.proveedorSeleccionado = null;
_this.proveedores = [];
PrivateRestauranteService.getProveedores().then(
function(proveedores){
_this.proveedores = proveedores;
},
function(error){
_this.proveedores = [];
}
);
$scope.$watch('cntrl.proveedorSeleccionado', function(newValue,oldValue){
if (newValue && newValue!=oldValue){
_this.PrivateIngredienteService.getIngregienteDTO().id = _this.proveedorSeleccionado.id;
}
}, true);
}]);
The following is the directive:
.directive('comboDirective', [
function(){
return {
restrict : 'E',
templateUrl: 'resources/js/private/views/utils/combo/combo.html',
scope : {
seleccionado : '=',
elementos : '=',
descripcion : '#'
}
}}]);
The combo.html:
<div class="col-xs">
<label translate>{{descripcion}}</label>
</div>
<div class="col-xs">
<div class="selectStyle">
<ui-select ng-model="seleccionado" theme="bootstrap" register-custom-form-control disable-validation-message="" required>
<ui-select-match placeholder="{{'input.seleccionar' | translate}}">{{$select.selected.descripcion}}</ui-select-match>
<ui-select-choices repeat="elemento in elementos | filter: $select.search">
<div ng-bind-html="elemento.descripcion | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
<i class="fa fa-chevron-down"></i>
</div>
</div>
And finally this is how i call the directive:
<div ng-controller="IngredientesDatosGeneralesController as cntrl">
<combo-directive
seleccionado="cntrl.proveedorSeleccionado"
descripcion="formulario.proveedor"
elementos="cntrl.proveedores">
</combo-directive>
</div>
Thanks in advance

I found what was happening... i don't know why but if i put this config in the directive and using the 'cntrl.' prefix before the "seleccionado" and "elementos" values in the HTML, it works properly.
'use strict';
angular.module("private.directives")
.directive('comboDirective', [
function(){
return {
restrict : 'E',
templateUrl: 'resources/js/private/views/utils/combo/combo.html',
scope : {
seleccionado : '=',
elementos : '=',
descripcion : '#'
},
controller : ['$scope', function ($scope) {
}],
controllerAs : 'cntrl',
bindToController: true
}
}]);

Related

How to pass a value from one controller to another controller? -angularjs

I am getting a value from a function in controller1. I want to set this value to controller2's directive's template. I am confused on how to use scope in this to achieve the same.
Html
<body ng-app = "myApp" ng-controller="parentcontroller as ctrl">
<div class ="boardcanvas" style ="overflow-x:auto;">
<div id = "board">
<list-wrapper ng-repeat="task in ctrl.tasks track by $index" class ="listwrapper" id = "listwrapper"></list-wrapper>
<add-list-controls class = "controls" tasks ="ctrl.tasks" ng-show ="ctrl.listcontrolarea"></add-list-controls>
</div>
</div>
</body>
Controller1 and its Directive:
angular.module('myApp')
.controller('listController', ['$scope','$compile','$http', function($scope, $compile, $http){
'ngInject';
$scope.tasks=[];
$scope.cardarr =[];
vm.addme = function(){
console.log(vm);
console.log($scope.tasks);
$scope.tasks.push({title: $scope.title, cardarr: []});
}
}])
.directive('addListControls', function() {
return {
restrict: 'E', // Element directive'
controller: 'listController as listctrl2',
scope: { tasks: "=",
cardarr: "="},
template: `<textarea ng-model= "listctrl2.title" placeholder ="Add a List" id="input" style = "position:absolute"></textarea>
<button id="controlbutton" class ="btn btn success" style = "position:absolute" ng-click="listctrl2.addme()">Save
</button>`,
};
});
Controller2 and its Directive:
.controller('listWrapperController', ['$scope','$compile','$http', function($scope, $compile, $http){
'ngInject';
$scope.tasks=[];
}])
.directive('listWrapper', function() {
return {
restrict: 'E', // Element directive
scope:{
tasks: '='
},
controller: 'listWrapperController as listctrl',
template: `<div id="titlebox">
<b class ="card1" tasks="listctrl.task" id ="cardtitle">
{{task.title}} // set the value from controller1 here
</b>
</div> `
};
});
You are initalizing the $scope.tasks array on both controllersand this override the original $scope.task reference (ctrl.tasks) that is set by directive definititon.
Try comment or initialize only if undefined
$scope.tasks= $scope.tasks || [];
$scope.cardarr = $scope.cardarr || [];
The listWrapper directive must be changed andd translate the ng-repeat to its template:
HTML
<list-wrapper tasks="ctrl.tasks"></list-wrapper>
listWrapper directive
(...)
.directive('listWrapper', function() {
return {
restrict: 'E', // Element directive
scope:{
tasks: '='
},
controller: 'listWrapperController as listctrl',
template:
`<div id="titlebox" ng-repeat="task in tasks track by $index" class ="listwrapper" id = "listwrapper">
<b class ="card1" id ="cardtitle">
{{task.title}} // set the value from controller1 here
</b>
</div> `
};
});

Why is 'this' different things in two different functions?

I have a problem where this isn't what I expect it to be, but I can't figure out why after hours of ripping my hair out. From what I understand this should be the same this in both functions, but it isn't. This causes my custom select to not update the label when you select something else in the dropdown because getCurrentOption always returns undefined since it tries to access .currentOption from the wrong this object.
Can someone explain what is happening here? And how to make it so that I pass the same this object to the two functions?
co-select.html:
<div class="co-select-form-control">
<label class="co-inset-label" ng-bind="label"></label>
<ul class="list-reset" ng-class="{'co-select': !label, 'co-select-labeled': label}">
<li ng-click="co.toggleSelect(this)" class="co-select-option clickable">
<span ng-bind="co.getCurrentOption(this) || default"></span>
<ul ng-show="co.isSelectToggled(this)" class="list-reset bg-light co-select-dropdown">
<li ng-repeat="option in list" ng-if="option !== co.getCurrentOption(this)"
ng-click="co.selectOption(this, option)" ng-bind="option" class="co-select-option"></li>
</ul>
<span class="co-select-icon">
<i class="icon icon-keyboard-arrow-{{co.isSelectToggled(this) ? 'up' : 'down'}}"></i>
</span>
</li>
</ul>
</div>
co-select directive:
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
The controller:
coForms.controller('CoFormsCtrl', [function() {
var coForms = this;
/* View functions */
coForms.toggleSelect = function(select) {
select.isToggled = !select.isToggled;
};
coForms.isSelectToggled = function(select) {
return select.isToggled ? true : false;
};
coForms.selectOption = function(select, option) {
select.currentOption = option;
console.log(select);
};
coForms.getCurrentOption = function(select) {
console.log(select);
return select.currentOption;
};
}]);
The console.log from within coForms.getCurrentOption shows this to be:
While the console.log from within coForms.selectOption shows this to be:
How I use this directive:
<co-select list="['option 1', 'option 2', 'option 3']" default="'option 1'"></co-select>
In Angular expressions this is a special word and refers to the scope on which the expression is being evaluated. Since ng-repeat creates a new scope, scope inside and outside it will be different.
Using this in expressions can be rarely required, and this case isn't an exception.
That's because ng-repeat creates a new scope (if i'm correct, it extends the parent scope), your selectOption is called inside ng-repeat so this represents that scope.
You shouldn't be using controller functions this way (passing this as parameter). You should make a variable "select" in your scope and use that.
This should work (see this plunkr: http://plnkr.co/edit/Javqd1zoKbubHEUD2Ea9?p=preview ) :
coForms.directive('coSelect', [function() {
return {
restrict: 'E',
scope: {
default: '=',
list: '=',
label: '#'
},
controller: 'CoFormsCtrl',
controllerAs: 'co',
templateUrl: 'app/views/components/co-forms/co-select.html',
link: function(scope, element, attrs) {
}
};
}]);
coForms.controller('CoFormsCtrl', ['$scope', function($scope) {
var coFormsCtrl = this;
coFormsCtrl.select={
isToggled: true,
currentOption:$scope.default
};
/* View functions */
coFormsCtrl.toggleSelect = function() {
coFormsCtrl.select.isToggled = !coFormsCtrl.select.isToggled;
};
coFormsCtrl.isSelectToggled = function() {
return coFormsCtrl.select.isToggled ? true : false;
};
coFormsCtrl.selectOption = function(option) {
coFormsCtrl.select.currentOption = option;
console.log(coFormsCtrl.select);
};
coFormsCtrl.getCurrentOption = function() {
console.log(coFormsCtrl.select);
return coFormsCtrl.select.currentOption;
};
}]);
And the template:
<div class="co-select-form-control">
<label class="co-inset-label" ng-bind="label"></label>
<ul class="list-reset" ng-class="{'co-select': !label, 'co-select-labeled': label}">
<li ng-click="co.toggleSelect()" class="co-select-option clickable">
<span ng-bind="co.getCurrentOption() || default"></span>
<ul ng-show="co.isSelectToggled()" class="list-reset bg-light co-select-dropdown">
<li ng-repeat="option in list" ng-if="option !== co.getCurrentOption()"
ng-click="co.selectOption(option)" ng-bind="option" class="co-select-option"></li>
</ul>
<span class="co-select-icon">
<i class="icon icon-keyboard-arrow-{{co.isSelectToggled() ? 'up' : 'down'}}"></i>
</span>
</li>
</ul>
</div>

angularjs ui-select trouble with creating custom directive

Good day! I need to create directive with ui-select inside.
Here is directive's controller and template:
app.controller("servicesCtrl", ["$resource", '$scope', function($resource, $scope){
$scope.clinicServices = [];
console.log($scope.data);
var Services = $resource(window.baseurl + "services/getAllbyClinic", null, {
get:{responceType: "JSON", isArray: false, method: 'POST'}
});
$scope.refresh = function(service){
if(service.length < 1) return false;
Services.get({service: service},function(data){
$scope.clinicServices = data.services;
});
};
}]);
<ui-select
multiple
tagging
tagging-label="false"
ng-model="$parent.data"
theme="bootstrap"
title="Выберите услугу"
search-enabled="true"
>
<ui-select-match placeholder="Введите услугу">{{$item.name}}</ui-select-match>
<ui-select-choices repeat="clinicService in clinicServices" refresh="refresh($select.search)">
<div ng-bind-html="(clinicService.name | highlight: $select.search)"></div>
</ui-select-choices>
</ui-select>
{{data}}
And here is directive initialization.
app.directive('services', function() {
return {
restrict: 'E',
templateUrl: window.baseurl + 'views/services',
scope: {
data: "=ngModel"
},
controller: "servicesCtrl",
controllerAs: "services"
}
});
<services ng-model="meEvent.services"></services>
Can't render $scope.data into view from servicesCtrl to edit previously selected data. There are an object inside $scope.data, but empty view's field. I tried ng-model="$parent.data" and ng-model="data", but have no effect.
There where errors in my version of ui-select library

AngularJS Wrapping a ui-select in a custom directive

I'm trying to wrap a ui-select in a custom directive. (https://github.com/angular-ui/ui-select)
this.adminv2.directive('eventSelect', function() {
return {
restrict: 'E',
replace: true,
scope: {
ngModel: '=',
placeholder: '='
},
controller: function($scope, $http) {
return $scope.refreshEvents = function(searchTerm) {
return $http.get('/events/autocomplete', {
params: {
term: searchTerm
}
}).then(function(response) {
return $scope.events = response.data;
});
};
},
template: "<div>{{ngModel}}\n <ui-select ng-model=\"ngModel\"\n theme=\"bootstrap\"\n ng-disabled=\"disabled\"\n reset-search-input=\"false\">\n <ui-select-match placeholder=\"Enter an event\">{{$select.selected.name}}</ui-select-match>\n <ui-select-choices repeat=\"event in events track by $index\"\n refresh=\"refreshEvents($select.search)\"\n refresh-delay=\"0\">\n <span ng-bind-html=\"event.name | highlight: $select.search\"></span>\n <i class=\"icon-uniF111 fg type-{{raceType}} pull-right\" ng-repeat='raceType in event.racetypes'></i>\n <br>\n {{event.dates}} <i class='pull-right'>{{event.location}}</i>\n </ui-select-choices>\n </ui-select>\n</div>"
};
});
The select works properly, but the binding with ng-model doesn't work. I cannot set the model or read it.
I don't get it since it works when I use a simple template such as
<div><input ng-model="ngModel"></div>
Is there something special to do because I wrap a directive in directive ?
I managed to make the binding work by setting the ng-model in the template as
ng-model="$parent.ngModel"

Can I create a directive that will allow me to create a HTML content block?

I have code like this:
<div>
<span>Type</span>
<select id="contentTypeSelect"
ng-change="home.configChanged(true)"
ng-model="ctrl.configService.admin.contentTypeId"
ng-options="item.id as item.name for item in ctrl.contentType.dataPlus"> </select>
</div>
It's very simple HTML but enclosing a <select> in a <div> with a <span> in that position is something I do many times. Is there a way I could create a directive that I could call like this and still have all the scopes working correctly in the actual HTML that the directive produces.
<div data-form-select>
label='Type'
id="contentTypeSelect"
ng-change="home.configChanged(true)"
ng-model="ctrl.configService.admin.contentTypeId"
ng-options="item.id as item.name for item in ctrl.contentType.dataPlus"
</div>
One more question. If I do this then is this something that's commonly done? I would like to abide by best practices and so far I have not really found many good examples of using Directives to do what I would like to do.
Thanks
I think this one is common for working all the directive
js file
angular.module("directiveAPP", [])
.controller("directiveController", function ($scope, $rootScope) {
var admin = [];
var configService = {
"admin": admin
};
var contentType = {
"dataPlus": [{
"id": 1,
"name": "ABC"
}, {
"id": 2,
"name": "DEF"
}, {
"id": 3,
"name": "GHI"
}]
};
$scope.home = {};
$scope.ctrl = {
"configService": configService
};
$scope.ctrl.configService.admin["contentTypeId"] = 0;
$scope.ctrl["contentType"] = contentType;
$scope.home.configChanged = function (data) {
};
}).directive("formSelect", function () {
return {
restrict: 'AE',
require: 'ngModel',
scope: {
label: '=',
id: '=',
ngChange: "&",
ngModel: "=",
options:"="
},
template: '<div><span>{{label}}</span><select id="{{id}}" ng-change="ngChange()" ng-model="ngModel" ng-options="item.id as item.name for item in options"></div>',
controller: function ($scope, $element, $attrs) {
$scope.label = $attrs.label;
$scope.id = $attrs.id;
}
};
});
html file
<div ng-app="directiveAPP">
<div ng-controller="directiveController">
<data-form-select
data-label="Type"
data-id="contentTypeSelect"
data-ng-change="home.configChanged(true)"
ng-model="ctrl.configService.admin.contentTypeId"
data-options="ctrl.contentType.dataPlus"></data-form-select>
{{ctrl.configService.admin.contentTypeId}}
<div ng-repeat="item in ctrl.contentType.dataPlus | filter:{'id':ctrl.configService.admin.contentTypeId}">
id : {{item.id}}</br>
name : {{item.name}}
</div>
</div>
</div>
FIDDLE
To expand on Josep's comment, yes you can and here is an example that will do what you want it to.
The key thing to share scope is to declare scope:false inside the directive. This means you will use the parent scope. Then all you need to do is include the template you need. e.g.
.directive('dataFormSelect', function() {
return {
scope: false,
replace: true,
template: '<div>\
<span>Type</span>\
<select id="contentTypeSelect"\
ng-change="home.configChanged(true)"\
ng-model="ctrl.configService.admin.contentTypeId"\
ng-options="item.id as item.name for item in ctrl.contentType.dataPlus">\
</select>\
</div>'
};
});
To use, you would just have this:
<div data-form-select>
</div>
As you have declared all the other ng attributes inside the directive
In addition to the CaspNZ answer you can separate out concerns means that presentation html like this:
.directive('myDirective', function() {
return {
restrict: 'E',
templateUrl: 'my-directive-content.html'
};
});
Then you need my-directive-content.html
<div>
<span>Type</span>
<select id="contentTypeSelect"
ng-change="home.configChanged(true)"
ng-model="ctrl.configService.admin.contentTypeId"
ng-options="item.id as item.name for item in ctrl.contentType.dataPlus">
</select>
</div>
And then you can you this "my-directive" custom directive in your angular application.

Resources