2

I need to perform a link function in a directive after an http response returns. The idea is something like this:

<input type="text" my-field>
<script>
 angular.module("mine")
 .controller ('myCtrl', function ($scope) {
  $http.get("/my/service").success(function (data, status, headers, config) {
    // OK, done with the query... now I know my field name to bind to. Somehow
    // I have to get it down to the link function below...
  });
 })
 .directive ('myField', function ($compile) {
  return {
    link: function (scope, element, attrs) {
      var my_field = attrs.myField;
      element.removeAttr('my-field');

      // Somehow figure out the field here in ngFieldSpec
      element.attr('ng-model', ngFieldSpec);
      $compile(element)(scope);
    };
   });
</script>

Here, I need to bind the input field to an element of the response, but I don't know what the element will be called until I get the response. But when I run it, the directive's link runs before $http gets done: the actual sequence is

  • $http.get starts
  • directive's link function run
  • $http.get returns success

I'm somewhat familiar with $q, but am not sure how that would be used to do what needs to be done. BTW, I have shown only one input field invoking the myField directive, but there are potentially many of them on the page, and they all need the same information.

Edited to add more information in response to request:

I have a service that returns a JSON data structure. I do not know in advance exactly what that data structure will look like, but I can figure it out and match the fields up with my page's input fields. I'm attempting to do this matching up in the link function. I'm glad to do it somewhere else; I could do it in the $http.success function, but that would be doing DOM manipulation in a controller; and my understanding is that DOM manipulation should only be done in a directive.

Here's what my HTML needs to look like:

<input type="text" my-field="[MY_EXTENSION_NAME]myFieldName">
<input type="text" my-field="[MY_EXTENSION_NAME]myFieldName2">
<input type="text" my-field="[MY_EXTENSION_NAME_2]myFieldName">

The response from the server will be something like:

{
    realField1: "Diddly",
    realField2: "Squat",
    extensions: [
      {
        name: "MY_EXTENSION_NAME",
        fields: [
          { name="myFieldName" value="Foo" },
          { name="myFieldName2" value="Bar" }
        ]
      },
      {
        name: "MY_EXTENSION_NAME_2",
        fields: [
          { name="myFieldName" value="Baz" },
          { name="myFieldName2" value="Buz" }
        ]
      }
    ]
 }

The server's response may vary because:

  • There may be any number of extensions ("MY_EXTENSION_NAME", etc.)
  • The extensions may be returned in any order
  • There may be any number of fields
  • The fields may be returned in any order

The whole problem here is I want to convert "[MY_EXTENSION_NAME]myFieldName" into the ng-model "model.extensions[0].fields[0].value. However, I am now thinking transforming the data into a canonical form during reading will be easier, so ng-model can just be "model.my_extension_name.myFieldName".

4
  • Not sure, but I think that $watch could help. Commented Jan 11, 2014 at 19:25
  • why are you using $compile inside a link function? you can probably achieve what you need with an isolated scope using '@'. please describe what you want to do. Commented Jan 11, 2014 at 19:32
  • Sorry, I thought I did explain what I want to do. I'll update. Commented Jan 11, 2014 at 19:40
  • By the way, I am calling $compile in link because that was suggested by somebody else in another SO and it works, but I'd be happy to have a better way to do that, also. Commented Jan 11, 2014 at 20:04

1 Answer 1

5

It is not clear what you are trying to achieve (I'm pretty sure there will be some better way), but you could do it like this:

1.
Define a promise in your scope:

app.controller('myCtrl', function ($http, $scope) {
    $scope.model = {
        promise: $http.get('/my/service'),
        myField01: 'Hello, world, from 01 !',
        myField02: 'Hello, world, from 02 !',
        myField03: 'Hello, world, form 03 !'
    };
});

2.
From your HTML, reference that promise in order to pass it to your directive:

<input type="text" my-field="model.promise" />

3.
Get this promise into your directive's isolate scope:

app.directive ('myField', function ($compile) {
    return {
        scope: { promise: '=myField' },
        ...

4.
In your link function, register a callback for when the promise gets resolved (i.e. you get a response to your request) and do all necessary manipulation:

...
link: function (scope, elem, attrs) {
    scope.promise.success(function (data) {
        elem.removeAttr('my-field');
        elem.attr('ng-model', 'model.' + data.fieldName);
        $compile(elem)(scope.$parent);
    });
}

See, also, this short demo.

Sign up to request clarification or add additional context in comments.

9 Comments

Thanks for the concrete suggestion. On the bigger picture, I am pretty new to Angular and I am happy to take alternate suggestions. What would that better way be? I previously edited my question to make it clear exactly what it is I'm trying to do - I need to bind some input fields to the data, but I don't know exactly what the data fields are called until the $http.get succeeds. I'm not sure how to explain it more clearly than that.
What I meant is that it is not clear what you are trying to achieve at a higher level. I.e. why you need to have some input fields that you don't know what to bind to etc.
Sorry. Because the server might return the data structured in different ways. There's a two-level hierarchy, and each level of the hierarchy has something like <item><name>foo</name><value>bar</value></item>. All I know in the attribute is "foo.bar"... so that might map to an ng-model attribute like (say) "model.extensions[3].fields[4].value. One alternate way be to remap the data into two levels of hashes and then back again when saving; but I don't think that would really help, because when the directive is called, extensions[3] would not yet exist. Suggestions are welcomed though. :-)
Instead of having some DOM elements you don't know what to bind to (thus having to wait for a response from the server to tell you what to bind each element to), why not receiving the data from the server and then insert the appropriate DOM elements (bound to the appropriate values) ? (Sorry, if my suggestion does not make sense - I must admit I can't 100% picture your setup yet.)
Thanks for staying with me - interesting idea. The thing is that the HTML author writes the DOM elements right into the Angular template, and he knows what the server will be returning at runtime; he just does not know exactly the data format. One possibility might be to have the author write the elements, but not create a directive on the fields at all, but just a regular attribute. Then after we read from the server, go through the fields, build the ng-model attributes, and $compile them. That means DOM manipulation in the controller, though. Still, it would be fairly minimal.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.