16

Is there any way to specify the default value for an @ binding of a component.

I've seen instruction on how to do it with directive: How to set a default value in an Angular Directive Scope?

But component does not support the compile function.

So, I have component like this:

{
  name: 'myPad',
  bindings   : {layout: '@'}
}

I want to free users of my component from having to specify the value of the 'layout' attribute. So..., this:

<my-pad>...</my-pad>

instead of this:

<my-pad layout="column">...</my-pad>

And... this 'layout' attribute is supposed to be consumed by angular-material JS that 'm using, so it needs to be bound before the DOM is rendered (so the material JS can pick it up & add the corresponding classes to the element).

update, some screenshots to clarify the situation:

Component definition:

{
  name : 'workspacePad',
  config : {
    templateUrl: 'src/workspace/components/pad/template.html',
    controller : controller,
    bindings   : {
      actions: '<', actionTriggered: '&', workspaces: '<', title: '@',
      flex: '@', layout: '@'
    },
    transclude: {
      'workspaceContent': '?workspaceContent'
    }
  }
}

Component usage:

<workspace-pad flex layout="column" title="Survey List" actions="$ctrl.actions"
  action-triggered="$ctrl.performAction(action)">
  <workspace-content>
    <div flex style="padding-left: 20px; padding-right: 20px; ">
      <p>test test</p>
    </div>
  </workspace-content>
</workspace-pad>

I want to make that "flex" and "layout" in the second screenshot (usage) optionals.


UPDATE

My "solution" to have this in the constructor of my component:

this.$postLink = function() {
  $element.attr("flex", "100");
  $element.attr("layout", "column");
  $element.addClass("layout-column");
  $element.addClass("flex-100");
}

I wish I didn't have to write those last 2 lines (addClass)... but well, since we don't have link and compile in component.... I think I should be happy with it for now.

Community
  • 1
  • 1
Cokorda Raka
  • 4,375
  • 6
  • 36
  • 54
  • If you are not requiring users to specify the layout attribute, why have it on the scope of the directive at all? – frishi Jan 04 '17 at 15:55
  • I need to have that 'layout' property in the tag generated for the component (my-pad). Otherwise the layout of my page will be messed up. And if user forget (by accident) to specify a value for it, I want to give it a default value ('column'). – Cokorda Raka Jan 04 '17 at 15:59
  • Are you using the `layout` scope attribute anywhere in your link function? Can you please post the code for the entire directive or at least the relevant parts? – frishi Jan 04 '17 at 16:02
  • hi frishi.... i just updated the post to add the screenshots of the code. Thanks! – Cokorda Raka Jan 04 '17 at 16:07
  • Thanks, but please refrain from posting screenshots of code. Always use the in-built markdown formatting or use services such as Plunkr or JSFiddle and post links to them. – frishi Jan 04 '17 at 16:09
  • btw... i'm using component (1.5), not directive. (i want to clear the path for future migration to angular 2) – Cokorda Raka Jan 04 '17 at 16:09
  • Provide your code, not screenshots, please. – Mistalis Jan 04 '17 at 16:14

4 Answers4

18

First of there is great documentation for components Angularjs Components`. Also what you are doing I have done before and you can make it optional by either using it or checking it in the controller itself.

For example you keep the binding there, but in your controller you have something like.

var self = this;

// self.layout will be the value set by the binding.

self.$onInit = function() {
    // here you can do a check for your self.layout and set a value if there is none
    self.layout = self.layout || 'default value'; 
}

This should do the trick. If not there are other lifecycle hooks. But I have done this with my components and even used it in $onChanges which runs before $onInit and you can actually do a check for isFirstChange() in the $onChanges function, which I am pretty sure will only run once on the load. But have not tested that myself.

There other Lifecycle hooks you can take a look at.

Edit

That is interesting, since I have used it in this way before. You could be facing some other issue. Although here is an idea. What if you set the value saved to a var in the parent controller and pass it to the component with '<' instead of '@'. This way you are passing by reference instead of value and you could set a watch on something and change the var if there is nothing set for that var making it a default.

With angularjs components '@' are not watched by the component but with '<' any changes in the parent to this component will pass down to the component and be seen because of '<'. If you were to change '@' in the parent controller your component would not see this change because it is not apart of the onChanges object, only the '<' values are.

Community
  • 1
  • 1
mjwrazor
  • 1,866
  • 2
  • 26
  • 42
  • I tried those callbacks, and while it I can add attr to the element (using $element.attr()), material JS didn't pick it up. I guess it's called too late in rendering cycle. – Cokorda Raka Jan 04 '17 at 16:33
  • I made an edit above its a bit of a side step in a different direction but I have also do this before to get something like this to work. – mjwrazor Jan 04 '17 at 16:37
  • Hi, it doesn't work for me. I wish angular had something like: flex: '@100' (whatever follows the @ will be taken as default value). for now i'll just make sure users (other progrmmers) write those attrs when using the component. – Cokorda Raka Jan 04 '17 at 16:48
  • maybe you could set up a separate binding not listed that has a default value. like layout: '@', layoutDefault: 'value'. and in the template have `layout={{ $ctrl.layout || $ctrl.layoutDefault }}`. And play around with that. Which I have done before as well haha. I am sort of confused why none of this is working. – mjwrazor Jan 04 '17 at 16:53
  • :) the thing is, in the parent template (user of component) I don't want to write that attr when calling out the component. i And specifying that attr in an inner div (inside the template definition of workspacePad) does not help either. those attr needs to be in < workspace-pad>; whether it's spelled out or not. – Cokorda Raka Jan 04 '17 at 17:02
  • There is no such thing as greater-than `>` binding. Perhaps you meant one-way `<` binding? For more information, see [AngularJS Comprehensive API Reference - scope](https://docs.angularjs.org/api/ng/service/$compile#-scope-). – georgeawg Jan 05 '17 at 00:20
  • Yeah what that guy said haha. Sorry I had it the wrong direction. – mjwrazor Jan 05 '17 at 15:36
  • The fact that component does not have this "link" hook (only postLink) makes thing a bit more difficult: http://stackoverflow.com/questions/26506841/change-angular-directive-element-attribute-dynamically – Cokorda Raka Jan 09 '17 at 03:04
  • another related post: http://stackoverflow.com/questions/39776460/how-to-use-compile-in-angular-component-angular-1-5-and-above ... so basically, for this scenario I was talking... I should've sticked with directive ?? (so I get the link and the $compile) – Cokorda Raka Jan 09 '17 at 03:14
  • Yeah. Directives are still useful in angular 1.5. In angular 2 components have the ability to use dom elements unlike 1.5 components that lack the link function. I still use directives for some of my items which deal with updating dom attributes with attr, and elem. – mjwrazor Jan 10 '17 at 14:21
  • I think you can shorten that by: self.layout = self.layout || 'default value'; – Steven Rogers Mar 01 '17 at 05:47
  • @flob answer is better than this one, because this one would not work with boolean when you want to give `false` value. – Moussa Nov 30 '17 at 09:08
  • `self.layout = ((self.layout === false) ? false : self.layout) || 'default value';` – mjwrazor Nov 30 '17 at 18:00
  • I want to down vote for calling the angular documentation 'great' Ive read much orf it word for word and you could add 3 times as much with the bits left out. – gbtimmon Feb 20 '19 at 14:58
6

To set the value if the bound value is not set ask if the value is undefined or null in $onInit().

const ctrl = this;
ctrl.$onInit = $onInit;
function $onInit() {
  if (angular.isUndefined(ctrl.layout) || ctrl.layout=== null)
    ctrl.layout = 'column';
}

This works even if the value for layout would be false.

flob
  • 3,760
  • 2
  • 34
  • 57
  • 1
    This solution is better than the accepted one because the accepted one doesn't work if the binding is a boolean for example. – Moussa Nov 30 '17 at 09:06
1

Defining the binding vars in constructor will just initiate the vars with your desired default values and after initialization the values are update with the binding.

    //ES6
    constructor(){
      this.layout = 'column';
    }
    $onInit() {
      // nothing here
    }
Anil Maharjan
  • 441
  • 4
  • 14
-1

you can use

$onChanges({layout}) {
    if (! layout) { return; }
    this.setupLayout(); ---> or do whatever you want to do
}
Tameshwar
  • 789
  • 1
  • 10
  • 17