4


[Info]
what I am trying to achieve is to implement some custom angular directives that will encapsulate all the JS needed for them to work.
The directives are unaware of what they will be displaying and where to store any input value coming from the user. These information will come from the attributes of the directive.
My directives will use the parent scope and won't create their own one.

[Problem] Since the directive does not know where in the $scope to map the ng-model and ng-bind, my approach is to read the directive's properties, identify what ng-model and ng-bing attributes shall be and set them to the corresponding elements. However this is not working.
I believe that its due to my lack of knowledge, so I am asking here - If my approach is OK?; Is it possible to set ngModel and ngBind in that manner?; What am I doing wrong?.

[My directive's code]

var directives = angular.module('test.directives', []);

directives.directive("labeledInput", function() {
return {
    restrict: 'E',
    scope: false,
    template: "<div>" +
                "<span class='label'></span>" +
                "<input class='input' type='text'></input>" +
              "</div>",

    link: function(scope, element) {
        var elementIdentifier = angular.element(element[0]).attr("idntfr");
        var elementClass = angular.element(element[0]).attr("element-class");
        var scopeValueName = angular.element(element[0]).attr("value-name");
        var defaultValue = angular.element(element[0]).attr("default-value");
        var elementLabel = angular.element(element[0]).attr("label");

        scope[scopeValueName] = defaultValue;
        scope[elementIdentifier] = elementLabel;

        $(angular.element(element[0]).children()[0]).attr('id', elementIdentifier);
        $(angular.element(element[0]).children()[0]).addClass(elementClass);
        $(angular.element(element[0]).children().children()[1]).attr('ng-model', scopeValueName);
        $(angular.element(element[0]).children().children()[0]).attr('ng-bind', elementIdentifier);
    }
};
});


[Result] As a result I see in the HTML page where the ng-model and ng-bind are binded at the right location, I have scope[scopeValueName] and scope[elementIdentifier] in the scope that Batarang provides, but I don't see them on my screen as values.

Have anyone ever solved a similar issue?

Thanks for your time!

[Edit] Sorry it seems my question wasn't understood I will add some details!

Here is the example HTML usage of my directive:

<labeled-input
    idntfr='id001'
    element-class='someClass'
    value-name='person_name'
    default-value='default'
    label='Person Name:'
>
</labeled-input> 

What I have in my browser after angular parse my directive and do it's jo is:

<div id="effect_dt" class="someClass">
    <span class="label" ng-bind="id001"></span>
    <input class="input" type="text" ng-model="person_name">
</div>

What I have in my controller scope is - $scope.id001 = "Person Name:" and $scope.person_name = default. However these values are not shown on the page at all.

Milen Igrachev
  • 259
  • 7
  • 17
  • I don't get what you're trying to do. Could you add 1) the html which will be used to add the directive to the view. 2) maybe a usable example (http://jsfiddle.net/) 3) a general description of what it is you're trying to achieve instead of what didn't work. (Also [DRY](https://en.wikipedia.org/wiki/Don't_repeat_yourself)) – Yoshi Jun 02 '14 at 12:40
  • @Yoshi sorry for my bad explanation. I added some info, I hope it clearer now. – Milen Igrachev Jun 02 '14 at 13:36

3 Answers3

1

If I understood you correctly you want to make something like:

<labeledInput>model-name and/or field name</labeledInput>

and convert it to something like:

<div>
    <span class='label' ng-bind="mode-name.field-name"></span>
    <input class='input' type='text'></input>
</div>

You have to read a bit more about angular compilation in directives, anyways:

  • to get any access to directive original content, like attributes or inner content you need to use transclude

  • to access template content (eg. in your case lets say 'span' element) you have to call tElement argument in compile method, cause it will hold your html from template

here are some fine examples: Angularjs: transclude directive template

all this operations like adding ng-model attribute-directive etc. you should add before directive compilation, to do so you have to use compilation block (in place of your 'link()' block):

.compile = function compile(tElement, tAttrs) {
    //here add some code eg append ng-model attribute etc.
    return {
    pre: function preLink(scope, iElement, iAttrs) {},
        post: function postLink(scope, iElement, iAttrs) {}
    }
}

and last part - controller scope, easiest way is just to add controller in template, so you wont loose any scope.

EDIT:

after reading your updated question ill give it a shot :p

app.directive('labeledInput', function($compile) {

  var directive = {};
  directive.transclude = true;
  directive.restrict =  'E';
  directive.template =  "<div>" +
                "<span class='label' ></span><br/>" +
                "<input class='input' type='text' ></input>" +
              "</div>";

  directive.compile =  function(cElem, cAttrs) {
    var scope=angular.element(cElem).scope();

    console.log(scope);

    var elementIdentifier = angular.element(cElem[0]).attr("idntfr");
    var elementClass = angular.element(cElem[0]).attr("element-class");
    var scopeValueName = angular.element(cElem[0]).attr("value-name");
    var defaultValue = angular.element(cElem[0]).attr("default-value");
    var elementLabel = angular.element(cElem[0]).attr("label");

     $(cElem[0]).find("div").attr('id', elementIdentifier).addClass(elementClass);

     $(cElem[0]).find("div span").attr('ng-bind', scopeValueName);
     $(cElem[0]).find("div input").attr('ng-model', elementIdentifier);
     return {
            pre: function preLink(scope, iElement, iAttrs) {
            scope[scopeValueName] = defaultValue;
      scope[elementIdentifier] = elementLabel;

            }
     };
    };

  return directive;
});

http://plnkr.co/edit/ImJmTHP3eQCzPaKfRTS4?p=preview

http://plnkr.co/edit/KIcfwsUGfCeg1UtioFv0?p=preview

Community
  • 1
  • 1
Marcus
  • 341
  • 1
  • 8
  • I agree with you I need to read some more. It seems you didn't understand my idea completely, but you gave me some form of an answer. I will try to use compile function instead of link one. Thanks for the information! On to it! (I edited the question you can see what I am trying to do clearer now :)) – Milen Igrachev Jun 02 '14 at 13:40
  • Ok that's it!. It is working and I believe I know why :), haha :). Thanks for the links and the example! – Milen Igrachev Jun 03 '14 at 07:02
0

I am not sure if am getting this right, But I guess you want to build some sort of generic directive with behaviour derived from the parent scope and bound to it's template, in this case, the input field.

You could create a isolate scope on the labeledInput directive, Like so:

scope: {
   doSomething:'&' 
}

And it's template would become :-

template: "<div>" +
            "<span class='label'></span>" +
            "<input class='input' type='text' ng-change="doSomething()"></input>" +
          "</div>",

And your html will be:-

    <labeledInput doSomething="someFunctionOnController()"></labeledInput>

So, someFunctionOnController defined in a controller's scope will be called on change of the input field.

viven
  • 27
  • 3
  • Well, nop :). I am trying not to use the parent scope at all. All the directive functionality shall be written inside it. The controller scope is suppose to be empty and to be aware of the directive just to include it in it's dependency list. However the directive will use the controller scope in save manner. – Milen Igrachev Jun 02 '14 at 13:58
  • Guess, you should correct the 6th line in ur question pal – viven Jun 02 '14 at 15:13
  • Ok my previous comment was totally inaccurate. I DO want to use the parent scope, but I don't want my directives to use isolate scope. Therefor I want to have ONLY one scope - the controller one. However I don't want to have any code inside the controller. Hence I need to add everything via controller/link properties of my directives. – Milen Igrachev Jun 03 '14 at 11:54
0

There is a much easier way to do this: (See the jsfiddle at http://jsfiddle.net/j55B8/17/)

Code for the directive:

var directives = angular.module('test.directives', []);

directives.directive("labeledInput", function() 
    return {
        restrict : "E",
        replace : true,
        scope : {
            "idntfr" : "@",
            "elementClass" : "@",
            "valueName" : "=",
            "defaultValue": "@",
            "label" : "@"
        },
        template : "<div id='{{idntfr}}' class='{{elementClass}}' ng-init='valueName=defaultValue'>" +  
                       "<span class='label' ng-bind='label'></span>" +
                       "<input class='input' type='text' ng-model='valueName'></input>" +
                   "</div>"
    }
});

HTML

<labeled-input 
    idntfr='id001' 
    element-class='someClass' 
    value-name='person_name' 
    default-value='default name' 
    label='Person Name: '>
</labeled-input>

The value typed in the above text box is {{person_name}}
rchawdry
  • 1,256
  • 1
  • 10
  • 14
  • I agree here! This is a good way to achieve my goals, however its mandatory for me to have scopeless directive not isolate scoped one. However for anyone reading this - the idea works just fine, may be this is your answer :)!. Thanks. – Milen Igrachev Jun 03 '14 at 07:01
  • If you have a scopeless directive, all of the `labeled-input` fields won't be able to maintain their own bindings to their respective fields. ie. If you have 10 `labeled-input` fields on a form that each have a different `value-name`, all 10 of them will only use the single `value-name` declared in the last instance (it will "overwrite" the others). The reason you have isolate scopes is so that each `labeled-input` field can maintain its own bindings to the fields it manages. – rchawdry Jun 03 '14 at 19:27