skip to Main Content

I expect that function step will be evaluated and its result will be passed right to the link function. But, when link function get called it has nothing in $scope.name variable. $args.checkStep is empty also. Is is by design?

HTML

<!DOCTYPE html>
<html ng-app="app" lang="en">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>angularjs link function doesn not get evaluated paramter</title>
    <link href="//cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/3.3.7/css/bootstrap.min.css" rel="stylesheet" />
</head>
<body>

<br>
<br>

<div class="container">
    <custom-include src="inc/homepage.html" prefix="homepage" suffix="" tag="release"></custom-include>
</div>

<script src="//cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.8/angular.min.js"></script>
<script src="app.js" type="text/javascript"></script>

</body>
</html>

JS

(function () {

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

    app.directive('customInclude', function () {
        return {
            restrict: 'E',
            scope: {
                type: '@'
            },
            link: function ($scope, $elem, $attr) {
                var prefix = $attr.prefix || '',
                    suffix = $attr.suffix || '';
                $scope.step = function (s) {
                    console.log('step', prefix, s, suffix);
                    return prefix + s + suffix;
                };
            },
            template: function (elem, attr) {
                return `
<h2>Fusce lorem ante</h2>
<ul>
    <li check-step="{{step(1)}}">Vestibulum efficitur</li>
    <li check-step="{{step(2)}}">Arcu vitae iaculis sodales</li>
    <li check-step="{{step(3)}}">Ligula ex interdum neque</li>
    <li check-step="{{step(4)}}">Ac iaculis felis lectus in purus.</li>
</ul>
`;
            }
        };
    });

    app.directive('checkStep', function () {
        return {
            restrict: 'A',
            scope: {
                name: '@checkStep'
            },
            link: function ($scope, $elem, $attr) {
                console.log('link', $scope.name);
            }
        };
    });

})();

—- Update —-

Why did the following code works:

<li check-step="{{1+step(1)}}">Vestibulum efficitur</li>

But this doesn’nt:

<li check-step="{{step(1)}}">Vestibulum efficitur</li>

?

JSBin
Plunker

2

Answers


  1. Chosen as BEST ANSWER

    It wont' work because link of an inner component get called always before of a link function of an outer component. The right place for exposing API for descendants is controller.

    The question

    Why did the following code works:

    <li check-step="{{1+step(1)}}">Vestibulum efficitur</li>
    

    is incorrect. This is not working, at least as expected. When AngularJS evaluates 1+step(1) it yields just 1 instead of 1 + <result of step(1)>. This is due to the fact that step function is not defined yet! Although isolated or inherited scope might be created (if it was specified).

    Here is what happened:

    1. Create a scope for custom-include. An isolated one with type field will be created, as it was specified in DDO ({scope: {type: '@'}}).
    2. Call custom-include.controller. This is the time where you can add some API for children components. But it was not specified in DDO, so it won't be called.
    3. Create a scope for check-step. An isolated one with name field will be created, as it was specified in DDO ({scope: {name: '@checkStep'}}).
    4. Call check-step.controller. Again, since it was not specified in DDO, nothing will be called.
    5. Call check-step.link. This time you can add some event listeners.
    6. Call custom-include.link.

    The solution is to move $scope.step = function (s) {...} into controller. That way, when check-step.link function will be called, step function will be defined in parent scope and will be called. The drawback of this methods is that when check-step.name get changed, check-step.link wont be notified about it.

    app.directive('customInclude', function () {
        return {
            restrict: 'E',
            scope: {
                type: '@'
            },
            controller: ['$scope', '$attrs', function ($scope, $attrs) {
                var prefix = $attrs.prefix || '',
                    suffix = $attrs.suffix || '';
                $scope.step = function (s) {
                    console.log('step', prefix, s, suffix);
                    return prefix + s + suffix;
                };
            }],
    

    ...

    To summarize:

    1. A scope get created.
    2. A controller get called.
    3. Any expressions get evaluated (context is a scope just created).
    4. If there are any descendants they will be initialized the same way.
    5. A link function get called.

  2. The attribute value is computed and put on the isolate scope after invocation of the link function. Use a $watch to see the eventual value:

    app.directive('checkStep', function () {
        return {
            restrict: 'A',
            scope: {
                name: '@checkStep'
            },
            link: function ($scope, $elem, $attr) {
                //console.log('link', $scope.name);
                //use $watch
                $scope.$watch("name", function(newValue) {
                    console.log('link', newValue);
                });
            }
        };
    });
    

    The DEMO on PLNKR

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search