Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

function templateUrl not $eval attributes? #7466

Closed
zxcabs opened this issue May 14, 2014 · 13 comments
Closed

function templateUrl not $eval attributes? #7466

zxcabs opened this issue May 14, 2014 · 13 comments

Comments

@zxcabs
Copy link

@zxcabs zxcabs commented May 14, 2014

Hi,
Attributes not $eval when use function in templateUrl.

Here example:

angular
    .module('app', [])
    .directive('drFoo', [
        function () {
            return {
                templateUrl: function (e, attr) {
                    console.log(attr.template); // => {{ template }}
                    return atrr.template;
                }
            };
        }
    ]);
<div ng-app="app" ng-init="template = 'foo.html'">
    <div dr-foo template="{{ template }}"></div>
</div>

jsfiddle

It reproduced in v1.2.16 and v1.3.0-beta8

@caitp
Copy link
Contributor

@caitp caitp commented May 14, 2014

When the templateUrl function runs, this is before interpolation happens (interpolation is actually a directive which is linked at priority 100).

I'm sure this is documented somewhere, but I'm not sure which page, so we should probably fix that in the docs and make it easier to find.

Anyways, interpolation during the templateUrl function does not help a lot, because you don't have a scope just yet. But if you want to, you could do something like this:

angular
    .module('app', [])
    .directive('drFoo', ['$interpolate', '$rootScope',
        function ($interpolate, $rootScope) {
            return {
                templateUrl: function (e, attr) {
                    var template = $interpolate(attr.template)($rootScope);
                    console.log(template); // => test.html
                    return template;
                }
            };
        }
    ]);
<div ng-app="app" ng-init="template = 'foo.html'">
    <div dr-foo template="{{ template }}"></div>
</div>

Here's an example http://plnkr.co/edit/8elM4ip1b7RccIhrEQBj?p=preview

@alexey-sh
Copy link

@alexey-sh alexey-sh commented Mar 27, 2016

<div ng-app="app" ng-init="template = 'foo.html'">
    <div dr-foo template="path/{{ template }}"></div>
</div>

this doesn't work..

@alexey-sh
Copy link

@alexey-sh alexey-sh commented Mar 27, 2016

controller('mainCtrl', function ($scope) {
    $scope.bar = {
      type: 'foo.html'
    }
  })
<body ng-controller='mainCtrl'>
    <div>
    <div dr-foo template="{{ bar.type }}"></div>
</div>
  </body>

Doesn't work too.

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

@gkalpak
Copy link
Member

@gkalpak gkalpak commented Mar 28, 2016

@alexey-sh, as explained by @caitp, at the time the template/templateUrl functions are executed, there is no scope for the directive to interpolate against, thus interpolation can't help you with the template.

The reason why manual interpolation doesn't work in your example is that you are interpolating against the $rootScope, but you have declared the template variable on the controller's scope (which is a child of the $rootScope).

Interpolation won't serve you well for specifying a template dynamically.

@alexey-sh
Copy link

@alexey-sh alexey-sh commented Mar 28, 2016

@gkalpak @caitp Your explanation is awesome, thanks.
But nobody can use template="path/{{ template }}" which is obviously way of ng templates and directives. I mean that anyone in their right mind will desire this one without deep knowledge of how the directive works within angular. Otherwise the ng template is used only for ng template and not for solving problems.
thanks

@gkalpak
Copy link
Member

@gkalpak gkalpak commented Mar 28, 2016

@alexey-sh, I don't think it is a common usecase at all. TBH, a directive which does not control its own template (i.e. it receives it externally as an interpolated value) sounds like a pretty bad idea.
Maybe you are approaching this the wrong way. What are you trying to achieve with passing the template as an interpolated value ? What problem are you trying to solve ?

@colthreepv
Copy link

@colthreepv colthreepv commented Jun 18, 2016

I would publish my use-case:

Imagine we have a big component, for example a TOS, or a long form that has 2 states: editable, or read-only. (Since we are on github, imagine opening README in read or write mode)

This state rw/ro can be passed as a simple attribute <long-component editable="$ctrl.maybe"/>
Being able to read the interpolated value in the template url, we can use 2 different HTML files.

A workaround for that would be create a long-component.html that ng-include dinamically one of the two long HTML files. But this seems kinda clunky in my book.

@orneryd
Copy link

@orneryd orneryd commented Jul 6, 2016

This should definitely be added. Being able to use bound values to determine template content is extremely valuable.

@bathos
Copy link

@bathos bathos commented Jul 6, 2016

FWIW, @colthreepv, ng-include served me well in a situation that may be similar. I had a large number of "inert" templates that nonetheless needed to be compiled so that they could reference text variables, URLs, etc, from scope. I had an array of objects describing FAQ Q/A pairs, and the answer parts were the templates that needed to be compiled. Each object had { question, answer, id }:

faqs.forEach(({ answer, id }) => $templateCache.put(`${ id }.html`, answer));

Then, within an ng-repeat on entry in $ctrl.faqs (the same array we used earlier), within the overall faq directive, each was available for use with ng-include:

<ng-include src="entry.id + '.html'"></ng-include>
@orneryd
Copy link

@orneryd orneryd commented Jul 6, 2016

Looking at the source, it shouldn't be too difficult to delay evaluation of the templateUrl to the nodeLinkFn and be able to inject the evaluated properties.

The usage should look something like:

templateUrl: function($element, $attrs, $bindings){
  //$attrs.property should be the usage string literal
  //$bindings.property should be the isolated scope reference instead.

},
bindings: {
  property: '='
},
@orneryd
Copy link

@orneryd orneryd commented Jul 6, 2016

@caitp it doesn't work for components at all though.

Looking at source the templateUrl function is executed at the registerComponent phase. If i am reading the source right, the $compile phase is the point at which the bindings are actually evaluated, which is far too late. It seems that evaluating bindings before attempting to determine the template is not trivial to change at this point.

@gkalpak
Copy link
Member

@gkalpak gkalpak commented Jul 6, 2016

@timothyswt , bindings are evaluated after the compile phase (during the linking phase, right before controllers are instantiated). The template has to fetched before the compile phase (you can't compile the template until you have a template).
So, this is not possible to change.

But there are several ways around this if you must (e.g. ngInclude, terminal directives and manual compilation etc).

@orneryd
Copy link

@orneryd orneryd commented Jul 6, 2016

@gkalpak hopefully Angular 2 will avoid this sort of pattern and follow more of a react-style evaluation of bindings so that we can have more dynamic components

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Linked pull requests

Successfully merging a pull request may close this issue.

None yet
7 participants
You can’t perform that action at this time.