Angular 1.5 component attribute presence - angularjs

I'm refactoring some angular directives to angular 1.5-style components.
Some of my directives have behavior that depends on a certain attribute being present, so without the attribute having a specific boolean value. With my directives, I accomplish this using the link function:
link: function(scope,elem,attrs, controller){
controller.sortable = attrs.hasOwnProperty('sortable');
},
How would I do this with the angular 1.5-style component syntax?
One thing I could do is add a binding, but then I'd need to specify the boolean value. I'd like to keep my templates as-is.

Use bindings instead of the direct reference to the DOM attribute:
angular.module('example').component('exampleComponent', {
bindings: {
sortable: '<'
},
controller: function() {
var vm = this;
var isSortable = vm.sortable;
},
templateUrl: 'your-template.html'
});
Template:
<example-component sortable="true"></example-component>
Using a one-way-binding (indicated by the '<') the value of the variable 'sortable' on the controller instance (named vm for view model here) will be a boolean true if set as shown in the example. If your sortable attribute currently contains a string in your template an '#' binding may be a suitable choice as well. The value of vm.sortable would be a string (or undefined if the attribute is not defined on the component markup) in that case as well.
Checking for the mere presence of the sortable attribute works like this:
bindings: { sortable: '#' }
// within the controller:
var isSortable = vm.sortable !== undefined;

Using bindings may work but not if you are trying to check for the existence of an attribute without value. If you don't care about the value you can just check for it's existence injecting the $element on the controller.
angular
.module('yourModule')
.component('yourComponent', {
templateUrl: 'your-component.component.html',
controller: yourComponentsController
});
function yourComponentController($element) {
var sortable = $element[0].hasAttribute("sortable");
}

There is a built-in way to do this by injecting $attrs into the controller.
JS
function MyComponentController($attrs) {
this.$onInit = function $onInit() {
this.sortable = !!$attrs.$attr.hasOwnProperty("sortable");
}
}
angular
.module("myApp", [])
.component("myComponent", {
controller: [
"$attrs",
MyComponentController
],
template: "Sortable is {{ ::$ctrl.sortable }}"
});
HTML
<my-component sortable>
</my-component>
<my-component>
</my-component>
Example
JSFiddle

Related

Passing keys of object to directive

I have created a directive as a wrapper for md-autocomplete so that it's easier to re-use. In the parent controller, I have an object. I want to pass the keys of the object to my custom directive, but I'm having trouble. Simplified code, without md-autocomplete:
Here's the script
var app = angular.module('myApp',[])
.controller('parentController', function(){
var parent = this;
parent.things = {item1: {color: "blue"}, item2: {color: "red"}};
})
.directive('childDirective',function(){
return {
scope: {},
bindToController: {
items:'&'
},
controller: childController,
controllerAs: 'child',
template: '<pre>{{child.items | JSON}}<pre>' //should be [item1,item1]
}
function childController(){
//Just a dummy controller for now
}
})
HTML
<div ng-app="myApp" ng-controller="parentController as parent">
<my-directive items="Object.keys(parent.things)">
</my-directive>
</div>
TL;DR: How do I pass the keys of an object defined in the parent controller to a child directive? I need to pass just the keys, not the object itself, because my directive is designed to deal with an array of strings.
Try using a directive with local scope from user attribute (=)
app.directive('childDirective', function() {
return {
replace: true,
restrict: 'E',
scope: {
items: '='
},
template: '<pre>{{items | JSON}}<pre>'
};
});
Using the directive, object in attribute "items" is passed "as is" , as a scope variable "items"
<div ng-app="myApp" ng-controller="parentController as parent">
<my-directive items="getKeys(parent.things)">
</my-directive>
</div>
Using Object.keys(obj) as source will cause an infinite loop digest (the function is always returning a new different object). You need a function to save the result to a local updatable object, like in this example:
https://jsfiddle.net/FranIg/3ut4h5qm/3/
$scope.getKeys=function(obj){
//initialize result
this.result?this.result.length=0:this.result=[];
//fill result
var result=this.result;
Object.keys(obj).forEach(function(item){
result.push(item);
})
return result;
}
I'm marking #Igor's answer as correct, because ultimately it led me to the right place. However, I wanted to provide my final solution, which is too big for a comment.
The search for the answer to this question led me to create a directive that is more flexible, and can take several different types of input.
The real key (and my actual answer to the original question) was to bind the items parameter to a proxy getter/setter object in the directive. The basic setup is:
app.directive('myDirective',function(){
return {
...
controller: localControl,
bindToController: {
items: '<' //note one-way binding
}
...
}
function localControl(){
var child = this;
child._items = [],
Object.defineProperties(child,{
items: {
get: function(){return child._items},
set: function(x){child._items = Object.keys(x)}
}
});
}
});
HTML
<my-directive items="parent.items">
<!-- where parent.items is {item1:{}, item2:{}...} -->
</my-directive>
Ultimately, I decided I wanted my directive to be able to accept a variety of formats, and came up with this plunk as a demonstration.
Please feel free to offer comments/suggestions on improving my code. Thanks!

Angular.js bindToController in directive: data, binded to controller, is not available as this.foo?

I've created a simplest possible directive with bindToController syntax, which resulted in a crysis of faith due to what I've seen:
function foobar() {
return {
restrict: 'E',
scope: {
foo: '='
},
template: "<div>foo = {{ vm.foo }}</div>",
bindToController: true,
controllerAs: "vm",
controller: ["$scope", function($scope) {
console.log(this); // print controller
console.log(this.foo); // print controller attribute
}]
};
}
Now, in html I say:
<foobar foo="1"></foobar>
and in the resulting html I see my div's content as expected:
foo = 1
But in the console I see:
controller
foo: 1
__proto__: Object
undefined
Ugh, what? So, somehow, foo is seen as an attribute of controller, but it is not available as this.foo like ordinary object properties. How do I access and modify it then? And what's going on with those 2-way binded data?
JSBin: https://jsbin.com/xidepewofe/edit?html,js,output
2-way binding expect variable not value, should be:
<div ng-init="myvarname = 1">
<foobar foo="myvarname"></foobar>
</div>
Another option is to change binding type to '#'.
In 1.6 change was introduced - by default bindings got resolved not immediately but before $onInit. So wrap your code in $onInit:
var vm = this;
vm.$onInit = function() {
console.log(vm.foo);
}
or change setting:
.config(function($compileProvider){
$compileProvider.preAssignBindingsEnabled(true)
});

Refresh the data in controller when scope gets changed in AngularJS

I'm pretty new with angular and I've read a lot of threads here and googled this topic but I cannot get a clear answer. what i am really trying to achieve is. lets suppose I have a controller A, this is a actual source for data. I passed it to one directive through binding it to a HTML. From this directive I am acually getting the source at another controller.
So I need to find out the way where I can change the data of controller when the data of controller A gets changed.
Controller A
angular.module('page.leadAndOpportunity.ctrl', []).controller('LeadAndOpportunityController', ['$scope', '$rootScope', '$timeout', function ($scope, $rootScope, $timeout, leadAndOpportunityService) {
$scope.selectDataSource = function (condition) {
var dataSource = [];
var dataSource = $scope.leadsDataSource.filter(function (item) {
return item.typeName === condition;
});
$scope.leadsDataSource = [];
$scope.leadsDataSource = dataSource;
console.log($scope.leadsDataSource);
}
}]);
HTML
<ng-senab-grid datasource="{{ leadsDataSource }}" defaultdata="{{defaultColumns}}" skipdata="{{ skipColumns }}" enablepersonalisation="true"></ng-senab-grid>
Directive
angular.module('page.gridView.drct', []).directive("ngSenabGrid", ["$rootScope", function ($rootScope) {
return {
restrict: "E",
templateUrl: "pages/gridView/page.gridView.tpl.html",
scope: {
enablePersonalisation: "#enablepersonalisation",
datasource: "#datasource",
defaultdata: "#defaultdata",
skipdata: "#skipdata"
},
}
}]
);
Controller B
var _datasource = JSON.parse($scope.datasource);
//rest the data follows
So when $scope.leadsDataSource gets changes on Controller A, then the
var _datasource = JSON.parse($scope.datasource);
also should get changed
I dont know if it is possible or not. But I need to change the data
Thanks in advance
remove the curly brackets of the variable.since this is a directive no need to add curly brackets
<ng-senab-grid datasource="leadsDataSource" defaultdata="defaultColumns" skipdata="skipColumns" enablepersonalisation="true"></ng-senab-grid>
if u want to get the value of the variable then use "=" if u use "&" it will only get the string
scope: {
enablePersonalisation: "=enablepersonalisation",
datasource: "=datasource",
defaultdata: "=defaultdata",
skipdata: "=skipdata"
},
also inject the directive module to ur angular module
angular.module('page.leadAndOpportunity.ctrl', ['page.gridView.drct'])
A simple explanation to keep in mind about different types of scopes would be below.
# Attribute string binding (String)
= Two-way model binding (model)
& Callback method binding (method)
According this you should be using Two-way binding instead of Attribute string binding because The model in parent scope is linked to the model in the directive's isolated scope. Changes to one model affects the other, and vice versa.
I would prefer using bindToController property definition in the directive. When set to true in a directive with isolated scope that uses controllerAs, the component’s properties are bound to the controller rather than to the scope.
That means, Angular makes sure that, when the controller is instantiated, the initial values of the isolated scope bindings are available on this, and future changes are also automatically available.
Check the Below sample fiddle example for more understanding
var myApp = angular.module('myApp', []);
myApp.controller('MyController', function($scope) {
$scope.change = function() {
$scope.fullname = 'Keshan';
}
$scope.reset = function() {
$scope.fullname = 'Fill Your Name';
}
});
myApp.directive('myDirective', function() {
return {
restrict: 'E',
scope: {
name: '='
},
controller: function($scope) {
this.name = 'Fill Your Name';
},
controllerAs: 'ctrl',
bindToController: true,
template: '{{ctrl.name}}',
};
});
<script src="https://code.angularjs.org/1.3.7/angular.js"></script>
<div ng-app="myApp" ng-controller="MyController">
<button ng-click="change()">Change</button>
<button ng-click="reset()">Reset</button>
<my-directive name="fullname"></my-directive>
</div>
Further Reading

Accessing ng-repeat scope on a custom directive

I'm having a go at a directive which will dynamically load a template based on a scope value passed into it.
I am using ng-repeat on my directive, and am using the iterated item as an attribute property:
<my-form-field ng-repeat="field in customFields" field="field">
In my directive I have the following code to set the template being used.
(function() {
'use strict';
angular
.module('app')
.directive('myFormField', myFormField);
function myFormField() {
var directive = {
restrict: 'E',
scope: {
field: '='
},
link: function(scope){
scope.getContentUrl = function() {
return 'app/modules/form_components/form_field/' + scope.field.type + '-template.html';
}
},
template: '<div ng-include="getContentUrl()"></div>'
};
return directive;
}
})();
Whilst the above works (which I found from other posts), I wonder if there is a better way.
For example I have seen examples of calling a function on the templateUrl config option instead, and then in that function access the scope attributes being passed in. When I tried this way, my field attribute was a literal 'field' string value (they are objects in my customFields array), so I think at that point the scope variables had not yet been evaluated.
With this current solution I am using, all of my templates get wrapped in an extra div since I am using ng-include, so I am just trying to make the rendered markup more succinct.
Any suggestions\advice is appreciated.
Thanks

Angular: How do I dynamically add ng-hide to a template that was loaded via templateUrl?

I'm trying to build a directive that reduces boilerplate for text fields, where the server-side declarations of things like field visibility can be passed in via a model.
I want to load the HTML for a general field from a templateUrl, transform the DOM of that (adding in various attributes and directives to this template) according to the model.
I've got it binding the proper ng-model to the nested input field, but when I try to apply an ng-hide to the top-level element, it shows up in the DOM but does no take effect.
If it were working properly, the code (so far) should be hiding the field, but it is not.
The code is at http://jsbin.com/AHoLAnUg/1/edit, and is reproduced below:
angular.module("directives", []).
directive('tuTextField',
function() {
return {
restrict: 'E',
replace: true,
compile: function(ele, attr) {
var element = jQuery(ele);
var input = jQuery(element.children('input')[0]);
// These work:
element.attr('id', attr.id);
element.attr('class', attr['class']);
// this fails: (I've tried element.attr() as well)
attr.$set('ngHide', attr.model + ".invisible['" + attr.field + "']");
// but this WORKS:
input.attr("ng-model", attr.model + ".fields." + attr.field);
},
templateUrl: '/AHoLAnUg/1.css'
};
}).
controller('v', [ '$scope', function(scope) {
scope.state = {
fields: {
name: "Tony"
},
invisible: {
name: true
},
readonly: {
name: true
},
validations: {
name: {
pattern: "^[a-zA-Z]",
message: "Must begin with a letter"
}
}
};
}]);
You shouldn't manipulate the root element of directive because compile function is called after $compile service finish its job, but you CAN manipulate child elements since they'll be compiled after their parent.
This is an example for directive execution order:
jsFiddle
That's why ngHide in your example won't take effect but ngModel will.
Try wrapping your template with another and manipulate them as you want.

Resources