Angular 1.5 Component: passing a function

All we need is an easy explanation of the problem, so here it is.

Is it possible to passing a function to a component and call this function inside the component passing a parameter?

Example:

List of posts

<post-list posts="blog.posts"
           loading="blog.loadingPosts"
           get-post-url="blog.getPostUrl" 
           is-user-authenticate="blog.user">
</post-list>

getPostUrl is a function (inside the container controller):

const getPostUrl = (postId) => {
    const protocol = $location.protocol();
    const host = $location.host();
    const port = $location.port();

    return protocol + "://" + host + "" + (port !== 80 ? ":" + port : "") + "/blog/post/" + postId;
};

List of posts: component

const PostList = {
  "bindings": {
    "posts": "<",
    "loading": "<",
    "getPostUrl": "&", //Function getPostUrl
    "isUserAuthenticate": "<"
  },
  "template": `<div>
                <div class="col-md-9 text-center" data-ng-if="$ctrl.loading">
                  <i class="fa fa-spinner fa-spin fa-2x"></i>
                </div>

                <div class="col-md-9 posts" data-ng-if="!$ctrl.loading">
                  <div data-ng-repeat="post in $ctrl.posts">
                    <post creation-date="{{post.creationDate}}"
                          content="{{post.content}}"
                          post-url="{{$ctrl.getPostUrl(post.creationDate)}}"
                          is-user-authenticate="$ctrl.user">
                    </post>
                  </div>
                </div>
              </div>`,
   "transclude": false
};

 angular
  .module("blog")
  .component("postList", PostList);

In this line:

post-url="{{$ctrl.getPostUrl(post.creationDate)}}" I want to call the function passing a parameter and this function is returning a string.

In post component (not PostList) the postUrl is a string attribute @.

But… Is not working!

angular.js:13550 Error: [$interpolate:interr] Can’t interpolate: {{$ctrl.getPostUrl(post.creationDate)}}
TypeError: Cannot use ‘in’ operator to search for ‘blog’ in 1459329888892
Error Link

Is it possible to do it? And how?

Thank you so much!

How to solve :

I know you bored from this bug, So we are here to help you! Take a deep breath and look at the explanation of your problem. We have many solutions to this problem, But we recommend you to use the first method because it is tested & true method that will 100% work for you.

Method 1

You can pass functions to components, but you must define the function arguments as object with the correct arguments names as its keys.
example:

<post-list posts="blog.posts"
           loading="blog.loadingPosts"
           get-post-url="blog.getPostUrl(postId)" 
           is-user-authenticate="blog.user">
</post-list>

const PostList = {
 "bindings": {
  "posts": "<",
  "loading": "<",
  "getPostUrl": "&", //Function getPostUrl
  "isUserAuthenticate": "<"
 },
 "template": `<post creation-date="{{post.creationDate}}"
                      content="{{post.content}}"
                      post-url="{{$ctrl.getPostUrl({postId:post.creationDate})}}">
                </post>

Method 2

If you want to call the function from inside a component and have it return a value then you need two-way binding:

"bindings": {
  "posts": "<",
  "loading": "<",
  "getPostUrl": "=", // <-- two-way binding
  "isUserAuthenticate": "<"
},

However, this is probably not very good idea. Consider passing data to component rather than making component request data from outside. This will make much better isolated component.

Method 3

To return a value to the binding function you must pass it as a object literal.
self.selected({id: ’42’, firstname: ‘Douglas’, lastname: ‘Adams’});

angular.module('webapp').component('myComponent', {
    templateUrl: 'myComponent.html',

    bindings: {
        selected: '&'
    },
    controller: function () {
        var self = this;

        self.someEvent= function(){
            self.selected({id: '42', firstname: 'Douglas', lastname: 'Adams'});
        };
    }
});

Afterwards you can access the object literal values by it’s properties.
id, firstname, lastname.
You can also pass additional parameters to the function. (myVariable)

<div>
    <span ng-init="myVariable='Universe'">
    <my-component selected="myFunction(id, firstname, lastname, myVariable)"></my-component>
</div>


$scope.myFunction = function(id, firstname, lastname, myVariable){
    console.log(id, firstname, lastname, myVariable);    
}

Method 4

According to Todd Motto’s Standards, it is not suggested to use ‘=’ instead try using ‘&’ that is passing a method from parent to child component and calling the method in the child will trigger back in the parent. Let’s take an example.

Parent Component’s template(HTML):

<child take-me-to-school="$ctrl.searchBikeKeyAndStart>

Child Component’s controller:

public someFunction = () => {
  this.takeMeToSchool();
}

Once the function is called from the child component’s controller then the function mapped in the parent will get triggered.

Parent Component’s controller(HTML):

public searchBikeKeyAndStart = () => {
  .....
}

When you want to pass an argument to the same function

Parent Component’s template(HTML):

<child take-me-to-school="$ctrl.searchBikeKeyAndStart(**key**)>

Child Component’s controller:

public someFunction = () => {
  this.takeMeToSchool({key: parameterValue});
}

Once the function is called from the child component’s controller then the function mapped in the parent will get triggered.

Parent Component’s controller(HTML):

public searchBikeKeyAndStart = (**key**) => {
  console.log(**key**) //will print the param passed
}

Note: Use and implement method 1 because this method fully tested our system.
Thank you 🙂

All methods was sourced from stackoverflow.com or stackexchange.com, is licensed under cc by-sa 2.5, cc by-sa 3.0 and cc by-sa 4.0

Leave a Reply