AngularJS - Share ngModel with nested directives and transcluded directives - angularjs

I am trying to create 3 directives:
.directive('dirOne', function () {
return {
restrict: 'E',
transclude: true,
replace: true,
controller: function ($scope, $element, $attrs) {
this.add = function (tag) {
tag && $scope.tags.push(tag);
};
},
template: '<div><p>Bucket from directive: {{tags}}</p><div ng-transclude></div></div>'
};
})
.directive('dirTwo', function () {
return {
restrict: 'A',
replace: true,
require: '^dirOne',
link: function (scope, element, attrs, dirOne) {
scope.add = function (tag) {
dirOne.add(tag);
};
},
template: '<div>'+
' <input type="text" ng-model="query" datum="sugestions" dir-three>' +
' <button ng-click="add(query)">add</button>' +
'</div>'
};
})
.directive('dirThree', ['$compile', function ($compile) {
var itemsTemplate = '<span class="sugestions"><span ng-repeat="item in datum|filter:query">{{item.name||item}}</span></span>';
return {
restrict: 'A',
transclude: true,
replace: true,
require: 'ngModel',
scope: {
datum: '=',
query: '=ngModel'
},
link: function (scope, element, attrs) {
var parent;
element.wrap('<span>');
parent = element.parent();
parent.append(angular.element($compile(itemsTemplate)(scope)));
}
};
}])
In the dirTwo and dirThree, I have an input <input type="text" ng-model="query" datum="sugestions" dir-three> with ngModel, this input needs to access and modify the content of ngModel, so that the scope is not isolated.
http://jsfiddle.net/joaoneto/hbABU/3/
Update
I Updated version, fix some mistakes I had committed, the content that was being transcluded in dirTwo, should not have the "ADD" function, and belongs to dirTree, hope it helps someone and apologize for peopl take to update this entry... see in http://jsfiddle.net/hbABU/4/

Related

Parent directive controller undefined when passing to child directive

I asked general question here in this post. I've got answer with working example; however when I try to use this example to modify existing code, I get error.
See my code below and in this Plunker page.
Html
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.3/angular.min.js"></script>
<div ng-app="myApp">
<tmp-menu ng-disabled="true">
<tmp-menu-link></tmp-menu-link>
<tmp-menu-link></tmp-menu-link>
</tmp-menu>
</div>
JavaScript(AngularJS):
angular.module('myApp', [])
.controller('MyDirectiveController', MyDirectiveController)
.directive('tmpMenu', function() {
return {
restrict: 'AE',
replace:true,
transclude:true,
scope:{
disabled: '=?ngDisabled'
},
controller: 'MyDirectiveController',
template: '<div>myDirective Disabled: {{ disabled }}<ng-transclude></ng-transclude></div>',
link: function(scope, element, attrs) {
}
};
})
.directive('tmpMenuLink', function() {
return {
restrict: 'AE',
replace:true,
transclude:true,
scope:{
},
required:'^^tmpMenu',
template: '<div>childDirective disabled: {{ disabled }}</div>',
link: function(scope, element, attrs, MyDirectiveCtrl) {
console.log(MyDirectiveCtrl);
scope.disabled = MyDirectiveCtrl.isDisabled();
}
};
})
function MyDirectiveController($scope) {
this.isDisabled = function() {
return $scope.disabled;
};
}
Inside directive tmpMenuLink, MyDirectiveCtrl is undefined.
Why is that?
You have a typo in your code:
required:'^^tmpMenu',
change it to
require:'^^tmpMenu',
Check this plunkr
https://plnkr.co/edit/DgyW3OFgr1GyAR8fuATi?p=preview
Because it's require not required.
angular.module('myApp', [])
.controller('MyDirectiveController', MyDirectiveController)
.directive('tmpMenu', function() {
return {
restrict: 'AE',
replace: true,
transclude: true,
scope: {
disabled: '=?ngDisabled'
},
controller: 'MyDirectiveController',
template: '<div>myDirective Disabled: {{ disabled }}<ng-transclude></ng-transclude></div>',
link: function(scope, element, attrs) {}
};
})
.directive('tmpMenuLink', function() {
return {
restrict: 'AE',
replace: true,
transclude: true,
require: '^^tmpMenu',
template: '<div>childDirective disabled: {{ disabled }}</div>',
link: function(scope, element, attrs, MyDirectiveController) {
scope.disabled = MyDirectiveController.isDisabled();
}
};
})
function MyDirectiveController($scope) {
this.isDisabled = function() {
return $scope.disabled;
};
}
<script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.5.3/angular.min.js"></script>
<div ng-app="myApp">
<tmp-menu ng-disabled="true">
<tmp-menu-link></tmp-menu-link>
<tmp-menu-link></tmp-menu-link>
</tmp-menu>
</div>

Function inside directive not being triggered

Why my function toggleCategory function is not being triggered?
Directive
app.directive('asideFilter', function() {
return {
restrict: 'E',
scope: {
toggleCategory: '=&onToggleCategory'
},
transclude: true,
templateUrl: 'assets/directives/asideFilter/asideFilter.html',
link: function(scope, element, attrs){
}
};
});;
Directive template
<div ng-click="toggleCategory({id: 'teste'})">TEST</div>
Parent scope
$scope.onToggleCategory = function () {
console.log('onToggleCategory');
}
Parent Caller
<aside-filter toggle-category="onToggleCategory({id: 'test '})" categories="categories" />
You can call the parent controller function like this also
app.directive('asideFilter', function() {
return {
restrict: 'E',
scope: {
toggleCategory: '&',
},
transclude: true,
templateUrl: 'assets/directives/asideFilter/asideFilter.html',
link: function(scope, element, attrs){
scope.toggleCategoryClick = function (value) {
scope.toggleCategory(value);
}
}
};
});
Directive template
<div ng-click="toggleCategoryClick({id: 'teste'})">TEST</div>
Directive Element
<aside-filter toggle-category="onToggleCategory({id: 'test '})" categories="categories" />

Passing arguments to nested directives in Angular

I am trying to pass arguments from a parent directive to a child directive but the arguments seem to be undefined. Is there a right way to do this?
}).directive('flipper', function ($timeout) {
return {
template: '<div class="flipper" ng-class="{big: big}"><div class="inner animated " ng-style="rotatorTop()"><ul><li ng-repeat="app in apps">{{app.name}}</li></ul></div></div>',
restrict: 'E',
replace: false,
scope: {
apps: "=",
big: "=",
sizes: "="
},
link: function ($scope, $element, $attrs) {
// undefined!
alert($scope.apps);
...
}
};
}).directive('slogan', function ($window) {
return {
template: '<div class="message"><div class="message-big"><div class="black">{{lines[0]}}<flipper apps="apps" class="big" sizes="flipperSize"></flipper>' +
'</div><div class="black">{{lines[1]}}</div><div class="black">{{lines[2]}}</div></div></div>',
restrict: 'E',
replace: false,
transclude: true,
scope: {
apps: "=",
lines: "="
},
link: function ($scope, $element, $attrs) {
...
}
};
});
Yes there is. As your code is quite incomplete, I've made this simple directive to demonstrate how you can pass values from the controller to a parent directive and a child directive.
myApp.directive('cdParent', function() {
return {
restrict: "AE",
template: "<div cd-child option=\"option\" text=\"text\">",
scope: {
option: "="
},
link: function(scope, elem, attrs) {
console.log("inside parent directive: " + scope.option); // will log the color for the controller
scope.text = "Hello nested directives";
}
}
});
myApp.directive('cdChild', function() {
return {
restrict: "AE",
template: "<pre style=\"color: {{option}}\">{{text}} ({{option}})</pre>",
scope: {
option: "=",
text: "="
},
link: function(scope, elem, attrs) {
console.log("inside child directive: " + scope.option);
}
}
});
also, the link function doesn't do dependency injection as a controller does, so it's arguments should be scope, element, attrs not $scope, $element, $attrs, argument order makes a difference here

AngularJS - accessing parent directive properties from child directives

This should not be too hard a thing to do but I cannot figure out how best to do it.
I have a parent directive, like so:
directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
replace: true,
transclude: true,
template: '
<div class="editable-fieldset" ng-click="edit()">
<div ng-transclude></div>
...
</div>',
controller: ['$scope', function ($scope) {
$scope.edit = ->
$scope.editing = true
// ...
]
};
});
And a child directive:
.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
template: function (element, attrs) {
'<div>
<label>' + attrs.label + '</label>
<p>{{ model.' + attrs.field + ' }}</p>
...
</div>'
},
require: '^editableFieldset'
};
});
How can I easily access the model and editing properties of the parent directive from the child directive? In my link function I have access to the parent scope - should I use $watch to watch these properties?
Put together, what I'd like to have is:
<editable-fieldset model="myModel">
<editable-string label="Some Property" field="property"></editable-string>
<editable-string label="Some Property" field="property"></editable-string>
</editable-fieldset>
The idea is to have a set of fields displayed by default. If clicked on, they become inputs and can be edited.
Taking inspiration from this SO post, I've got a working solution here in this plunker.
I had to change quite a bit. I opted to have an isolated scope on the editableString as well because it was easier to bind in the correct values to the template. Otherwise, you are going to have to use compile or another method (like $transclude service).
Here is the result:
JS:
var myApp = angular.module('myApp', []);
myApp.controller('Ctrl', function($scope) {
$scope.myModel = { property1: 'hello1', property2: 'hello2' }
});
myApp.directive('editableFieldset', function () {
return {
restrict: 'E',
scope: {
model: '='
},
transclude: true,
replace: true,
template: '<div class="editable-fieldset" ng-click="edit()"><div ng-transclude></div></div>',
link: function(scope, element) {
scope.edit = function() {
scope.editing = true;
}
},
controller: ['$scope', function($scope) {
this.getModel = function() {
return $scope.model;
}
}]
};
});
myApp.directive('editableString', function () {
return {
restrict: 'E',
replace: true,
scope: {
label: '#',
field: '#'
},
template: '<div><label>{{ label }}</label><p>{{ model[field] }}</p></div>',
require: '^editableFieldset',
link: function(scope, element, attrs, ctrl) {
scope.model = ctrl.getModel();
}
};
});
HTML:
<body ng-controller="Ctrl">
<h1>Hello Plunker!</h1>
<editable-fieldset model="myModel">
<editable-string label="Some Property1:" field="property1"></editable-string>
<editable-string label="Some Property2:" field="property2"></editable-string>
</editable-fieldset>
</body>
You can get access to parent controller by passing attribute in child directive link function
link: function (scope, element, attrs, parentCtrl) {
parentCtrl.$scope.editing = true;
}

Accessing parent's parent controllers through Angular require attribute

In the below example, the accordion-group directive shares a controller and scope with the parent accordion, using the "require: '^accordion'" attribute within the accordion-group directive.
If I wanted to create a child directive, under accordion-group, how would it access the accordion controller as well? Requiring ^accordion and ^accordionGroup do not seem to work.
https://github.com/angular-ui/bootstrap/blob/master/src/accordion/accordion.js
It does work that way. I was just being stupid.
Fiddle for posterity here.
'use strict';
angular.module('myApp', []).controller('OneController', function() {
this.test = function(element) {
element.css('color', 'red');
}
}).directive('one', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
controller: 'OneController',
template: '<span ng-transclude>And a </span>',
}
}).directive('two', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
require: '^one',
template: '<span ng-transclude>and a </span>',
}
}).directive('three', function() {
return {
restrict: 'E',
transclude: true,
replace: true,
require: '^one',
template: '<span ng-transclude>and a one two</span>',
link: function(scope, element, attrs, ctrl) {
ctrl.test(element);
}
}
});

Resources