Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Feature: Either add scope to attributes that are passed to templateUrl function or preprocess attributes based on scope parameters #2895

Open
spaceribs opened this issue Jun 6, 2013 · 37 comments

Comments

@spaceribs
Copy link
Contributor

As you can see in the following plunker, you cannot access a scoped value within the directive's templateUrl. All attributes are passed uncompiled, and there is no way to access the scope within this context. I have a workaround with a lot of dependencies injected within the link function of that directive (showing how it should work).

@btford btford closed this as completed Aug 24, 2013
@btford
Copy link
Contributor

btford commented Aug 24, 2013

As part of our effort to clean out old issues, this issue is being automatically closed since it has been inactivite for over two months.

Please try the newest versions of Angular (1.0.8 and 1.2.0-rc.1), and if the issue persists, comment below so we can discuss it.

Thanks!

@filiptc
Copy link

filiptc commented Sep 5, 2013

+1 to this feature request. Not being able to access scope from templateURL function nor getting scope-preprocessed attributes forces workaround use of link and manual $http loading of templates.

@spaceribs
Copy link
Contributor Author

Yes, please reopen this issue, it is still a missing feature.

@gregaton
Copy link

This is still a missing feature.
Please reopen.

@localpcguy
Copy link

This should be reopened. Either scope should be accessible as an attribute of the templateUrl function or scope bindings should be evaluated before attributes are passed to the function

@spaceribs
Copy link
Contributor Author

I think this is a similar issue to issue #1039, and essentially it boils down to the compileTemplateUrl being called before the scope is available. Please merge these issues.

@shox
Copy link

shox commented Aug 9, 2014

I'm struggling with this issue from two days, please reopen this issue.

@RichardLitt RichardLitt reopened this Aug 9, 2014
@jimmywarting
Copy link
Contributor

dosen't ngInclude works for you?
Perhaps something like this example

@spaceribs
Copy link
Contributor Author

@jimmywarting Yes you could do something like this, but that requires 2 compiles when only one should be needed. A directive should be self contained, not requiring other core directives to be used alongside it. Technically you could do the same thing by setting the templateUrl to '/views/loader.html' with:

<div ng-include="'/views/'+model.type+'-type.html'"></div>

but that now requires 2 ajax calls and 2 child scopes when you only really needed one.

Being able to grab a template type from the scope and apply it to the templateUrl dynamically is a long sought after feature that directives could benefit from, but I believe it's a "chicken before the egg" issue looking at the code myself, and would require a major revamp in the order of operations for compiling a directive.

@jeffbcross jeffbcross added this to the Backlog milestone Aug 13, 2014
@CxRes
Copy link

CxRes commented Aug 19, 2014

+1 to this request.

Desperately need this feature. The ngInclude trick is not at all ideal!

The same problem exists in ngRoute as well, which after all is a directive.
#2295

@vict-shevchenko
Copy link

+1 will be super usable to have compiled parameters in tempalteUrl function.

Thanks.

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

@vict-shevchenko the template function is run before scope is ever created, (so isolate scope propreties would never work) --- it would only work properly with the parent scope. I think people generally want this behaviour with isolate scope properties

@vict-shevchenko
Copy link

It seems that I want the same... that tAttrs.prop in templateUrl function are compiled with its parent scope.

so tAttrs.prop(templateUrl func) == iAttr.prop(link func);

but now tAttrs.prop is "{{prop}}" and iAttrs.prop is "propertyNameFromParentScope".

Is not this issue about it?

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

it's a terrible idea anyways

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

a directive template shouldn't depend on anything like that.

@vict-shevchenko
Copy link

Ok, can you then point me to a proper solution of the issue? My need is in dynamically change templateUrl based on parent scope parameters, that I can pass to a directive attributes.

Me situation is next, there is a directive that based on 'id' and 'type' (8 different types) displays some data. For each 'type' there is a different template, but same controller can be used.
My desire is to eliminate copy-pasting like using 8 ng-includes or similar.

So I think directive that can dynamically load proper html based on 'type' is a good idea.

my current solution is

link: function (scope, iElement, iAttrs) {
    var templateUrl= 'views/' + iAttrs.type + '.html';
    $http.get(templateUrl, {cache: $templateCache}).success(function(tplContent){
        iElement.append($compile(tplContent)(scope));
    });
}

is it also terrible by its nature? What approach you recommend to follow in such cases?

Thanks.

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

i recommend that you don't do this and instead use structural directives like ng-if or ng-switch or ng-include

@vict-shevchenko
Copy link

Thanks, will keep it in mind.

@spaceribs
Copy link
Contributor Author

@caitp this is a feature request because it doesn't make sense to me to have to rely on core directives or multiple ajax calls/scopes to make this work, can you elaborate your position?

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

it does make sense for you to rely on structural directives for this. what doesn't make sense is having a crazily dynamic behaviour that is hard to keep control of.

@vict-shevchenko
Copy link

ng-switch from 8 elements looks crazy. keeping track of behavior is my problem actually.

You guys already did support for function in templateUrl, so it already gives some space for dynamically change it.

With possibility to access real values from parent scope it will be even be more powerful. That is why I`d like to +1 for this feature request.

@spaceribs
Copy link
Contributor Author

If the purpose of a directive is to modularize functionality, we've been given the native ability to generate a templateUrl based on a function which receives only undigested element attributes. It may be difficult to change the functionality over to allow access to the scope, but it certainly makes more sense than to "stop there".

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

this request is not a good idea, this would be damaging to modularity

@caitp
Copy link
Contributor

caitp commented Oct 9, 2014

structural directives need to be dynamic like this, and don't need to have specific interactions with their templates --- but components should definitely never need to do this, and if they do, they should do it using structural directives instead

@spaceribs
Copy link
Contributor Author

...

So, this isn't a good feature because templateUrl: function(element, attr){ return "/template.html"; } should be completely divorced from it's parent or isolated scope?

@localpcguy
Copy link

I'm not exactly sure why dynamic templates are such a bad idea. If there is a directive that has the same logic but presents in different ways visually, why would you want to duplicate that logic into multiple directives? I don't like having to duplicate logic or to have to use control mechanisms in my HTML templates except where it really is necessary. Can you unpack why this is such a bad idea and hurts modularity please?

@caitp
Copy link
Contributor

caitp commented Oct 10, 2014

templates can (and do) contain logic and behaviour, not just presentation. Your directive should know exactly what it's going to have under the hood, and if it really can't know (ie, ui-view for instance), it should behave as a proper structural directive.

Components (say a custom button or a header or something) should know what their DOM is going to look like ahead of time.

Template functions allow a few things, like requesting a slightly different template URL depending on the deployment url (say I want to serve a different url depending on whether I'm hosted from gh-pages or from localhost, eg for a slide show). They can also serve different templates based on app configuration, which was proposed for ui-bootstrap last year. They basically let you configure an app for different deployments, or other things which are basically static, but you don't necessarily know ahead of time. They might want to use an attribute value, but this behaviour should be static, not runtime-changeable.

Trying to do too much dynamic stuff in the template and template URL functions is just going to create more headaches than anything else.

Yes, it would be a powerful feature, but not necessarily a useful one. The usefulness of it is greatly exaggerated by people who are most likely trying to solve the wrong problem with the wrong tool. (No offense, we've all done this.) In order to keep your app manageable, you should definitely minimize how dynamic your directives can be. Configuring templates (attached to behaviour) are going to be a nightmare, but if you really want to do that, ng-include already exists. ng-switch already exists.

That said, I don't know if anyone else agrees with me, maybe Igor thinks super-dynamic template functions are a great idea. But I feel like the end result of it is this:

  1. Creating yet more ways in angular to accomplish the same given task (runtime-dynamic presentation).
  2. Making it easier to shoot yourself in the foot by creating unmanageable directives.
  3. Making the directive API even more needlessly complicated than it already is, for dubious reasons.
  4. Harming performance by encouraging people to run even more complicated logic in their template functions during compilation of the DOM (which may happen many times over the course of an application).
  5. Moving the angular learning curve further down the X axis (creating even more to learn about for new developers who just want to make their super awesome VC-funded online-pokemon-trading-and-social-media web application and become superstars). Angular is already very complex and very powerful, this is increasing complexity but not so much power.

I don't know if anyone else agrees with the points I'm making here, but honestly if you feel you need super-dynamic template functions, you're probably taking the wrong approach to solving your problem, and you're probably mis-identifying the problem you're trying to solve.

@localpcguy
Copy link

Thanks for the detailed explanation, I understand what you are saying. Need to digest a little before I could say if I agree, but I appreciate the reasoning.

@spaceribs
Copy link
Contributor Author

@caitp Thanks for the detailed explanation, I can totally see your side of things. I can tell you that when I first started building directives in angular, the templateUrl function was one of the more confusing portions because everything else was so centered around the scope and it just didn't make sense to not be able to access the scope from it.

I don't think it should be necessarily stripped out to avoid this kind of confusion, but there is nothing in the directive docs about it, and it felt like I was hunting a sasquatch in the end.

@btford
Copy link
Contributor

btford commented Oct 10, 2014

there is nothing in the directive docs about it

would you like to submit a PR?

@spaceribs
Copy link
Contributor Author

@btford Yeah, I was planning on it, I'll submit something tonight after work.

spaceribs added a commit to spaceribs/angular.js that referenced this issue Oct 12, 2014
Adding documentation and example of using a templateUrl function to the directive developer guide.

Related to angular#2895
@slawo
Copy link

slawo commented May 5, 2015

@caitp I disagree on most points, not being able to use what seems like a basic feature <li ng-repeat="item in items"> ... <list-item ... modelName="{{item.modelName}}"/></li> is counter intuitive:

  • this pushes the learning curve further down the x axis as it creates another unnecessary exception to the expected behaviour and requires more work to accomplish a simple task.
  • It makes the directive API harder to use and requires more boilerplate and workarounds
  • It makes it harder to manage large applications where slight variations in the templates (served by the server if they exist) are required.
  • repeating the same boilerplate across files and projects definitely increases chances of shooting yourself in the foot. More so when paths are adjusted and new configuration options are added.
  • due to the added boilerplate, scopes and indirections the directives become harder to manage.

I am not even sure if this feature would harm performance but so far I believe readability and reducing maintenance costs are more important factors.

I agree on the fact that this is not needed in most applications but cases where this missing feature is glaring and simply difficult to work with do exist:

  • In one app I have multiple directives which are used in multiple views. They use the exact same template in most cases. But some server side modules can replace the base template for a given viewId or for a given model.
  • I have another application with dynamically generated models (public/private). In some cases models can be added by admin users, each admin user can define templates to list, display, preview, print and to some extent to add/edit the model entries.
    The code maintenance would increase to unmanageable proportions if I had to ng-switch across all the views/models available to a given user.

So far ng-include kind of works but it comes with its own problems and it adds unnecessary complexity, blunder and boilerplate to something that could be solved in one line: return consts.MODELS_PARTIAL_PATH +'/'+ (attrs.viewId || 'default') + "/" + (attrs.modelName || 'item') + '-list-entry.html';

Not to be a kill joy, but one of the reasons angular's learning curve is so bumpy is due to this type of exceptions. Angular promises great flexibility, but sometimes it stops short with no apparent reason but "It's better for you." requiring us to write boilerplate to get around missing features kept out for what seems like arbitrary reasons.

If the impact on performance would be so damaging that this feature justifies the increased complexity to get dynamic template urls working, well... OK then... but why not add an alternate findTemplateUrl which would give access to the scope variables for those who need this?

Now maybe I have overlooked something and what we need done can be achieved easily with less boilerplate across all those directives, or maybe directives are not the right tool for the job, in that case I would gladly take any advice on how I can fix this.

@poshest
Copy link
Contributor

poshest commented May 22, 2015

I have just converted some code that was being called by ngInclude into a directive for modularity reasons. My ngInclude had a suffix which instructs the server to send the template in the correct language. I would really like to add that parameter to the templateUrl so the correct language template is loaded.

This http://stackoverflow.com/a/23999356 doesn't work and this http://stackoverflow.com/a/21835866 is hacky.

@caitp would love to understand what you think is the 'angular' way for my use case. Dynamic templateUrl just seems like a no brainer to me.

@JosephCardwell
Copy link

+1 for this feature. Using ng-include inside a template to pass scope is an obvious cludge, and extra boilerplate is...what it is.

@spaceribs
Copy link
Contributor Author

@Narretz Understandable that this is marked as being in Purgatory. I haven't been keeping up with the latest in Angular 2 (currently in the middle of a big project) but has any design consideration for this been addressed within @Component?

@poshest
Copy link
Contributor

poshest commented Dec 19, 2015

Regarding my requirement (above), @Narretz offered a great solution which satisfies it in #13526. So my need for greater configurability in templateUrl has gone away. Thank you @Narretz!

icfantv added a commit to icfantv/deck that referenced this issue Jan 24, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 24, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 24, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 25, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 25, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 25, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 25, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
icfantv added a commit to icfantv/deck that referenced this issue Jan 25, 2017
* pull angular-cron-gen library internally to deck to fix several issues
with the library.
* the latest version of the library will not allow angular expressions
for the new `template-url` property as it uses the `templateUrl`
function which executes before the scope is initialized so any
scoped property value is not accessible.  this means that our template
value set via a controller property is not accessible.  this is a
long-standing issue [in the core library](angular/angular.js#2895)
and it doesn't look to be fixed anytime soon.
* an hourly cron trigger set in the UI to execute start at a specific
time actually generates a daily cron expression so the portion of the
hourly cron trigger that allows starting at a specific time was
removed from deck's cron picker template.
* the regex used by the library to match hourly cron expressions was
incorrectly matching daily cron expressions which was resulting the UI
displaying `hourly` instead of `daily` in the select so the regex for
hourly was fixed to properly test for matching hourly cron expressions.
@picheli20
Copy link

picheli20 commented Jan 30, 2017

@vict-shevchenko I used your solution, but for components it's not possible to use the link, so I left in $onChanges function.

this.$onChanges = function (obj) {
  const layout = require(`./templates/${obj.layout.currentValue}.html`);
  $element.append($compile(layout)($scope));
};

Thanks for the help!

Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
None yet
Development

No branches or pull requests