【问题标题】:NgUpgrade: Unable to use templateUrl when upgrading Angular1 componentsNgUpgrade:升级 Angular1 组件时无法使用 templateUrl
【发布时间】:2016-12-27 16:07:25
【问题描述】:

我想升级一个 ng1 组件以在 ng2 组件中使用。

如果我只使用要升级的 ng1 组件的模板字符串,它就可以工作。但是,如果我改用 templateUrl,应用程序会崩溃并给我这个错误:

angular.js:13920 Error: loading directive templates asynchronously is not supported
at RemoteUrlComponent.UpgradeComponent.compileTemplate (upgrade-static.umd.js:720)
at RemoteUrlComponent.UpgradeComponent (upgrade-static.umd.js:521)
at new RemoteUrlComponent (remote-url.component.ts:11)
at new Wrapper_RemoteUrlComponent (wrapper.ngfactory.js:7)
at View_AppComponent1.createInternal (component.ngfactory.js:73)
at View_AppComponent1.AppView.create (core.umd.js:12262)
at TemplateRef_.createEmbeddedView (core.umd.js:9320)
at ViewContainerRef_.createEmbeddedView (core.umd.js:9552)
at eval (common.umd.js:1670)
at DefaultIterableDiffer.forEachOperation (core.umd.js:4653)

这是一个演示我的问题的 plunk:

https://plnkr.co/edit/2fXvfc?p=info

我已遵循 Angular 1 -> 2 升级指南,看来这段代码应该可以工作。我不太确定为什么它不起作用。

【问题讨论】:

    标签: angularjs angular ng-upgrade


    【解决方案1】:

    我找到了一个相当便宜的解决方案。

    只需使用template: require('./remote-url.component.html') 而不是templateUrl: './remote-url.component.html',它应该可以正常工作!

    【讨论】:

    • 我试过你的方法,但我得到了这个错误:Uncaught ReferenceError: require is not defined at VM586 remoting-url.component.js:8
    • 要使用require你必须使用合适的模块加载器,在这种情况下我认为使用commonJS来获得更多细节。stackoverflow.com/questions/19059580/…
    • 尝试使用此处指定的 require('!raw-loader!./path-to-template.html') :github.com/angular/angular-cli/issues/…
    【解决方案2】:

    这真的很令人沮丧,因为 Angular 升级文档明确表示可以使用 templateUrl。从来没有提到这个异步问题。我通过使用 $templateCache 找到了解决方法。我不想更改我的 angular 1 指令,因为它用于我的 angular 1 应用程序并且也将被 angular 4 应用程序使用。所以我必须找到一种方法来即时修改它。我使用了 $delegate、$provider 和 $templateCache。我的代码如下。我还使用它来删除 replace 属性,因为它已被弃用。

    function upgradeDirective(moduleName, invokedName) {
        /** get the invoked directive */
        angular.module(moduleName).config(config);
    
        config.$inject = ['$provide'];
        decorator.$inject = ['$delegate', '$templateCache'];
    
        function config($provide) {
            $provide.decorator(invokedName + 'Directive', decorator);
        }
    
        function decorator($delegate, $templateCache) {
            /** get the directive reference */
            var directive = $delegate[0];
    
            /** remove deprecated attributes */
            if (directive.hasOwnProperty('replace')){
                delete directive.replace;
            }
    
            /** check for templateUrl and get template from cache */
            if (directive.hasOwnProperty('templateUrl')){
                /** get the template key */
                var key = directive.templateUrl.substring(directive.templateUrl.indexOf('app/'));
    
                /** remove templateUrl */
                delete directive.templateUrl;
    
                /** add template and get from cache */
                directive.template = $templateCache.get(key);
            }
    
            /** return the delegate */
            return $delegate;
        }
    }
    
    upgradeDirective('moduleName', 'moduleDirectiveName');
    

    【讨论】:

      【解决方案3】:

      在尝试使用 requireJS 和对我不起作用的文本插件后,我设法使用 'ng-include' 使其工作,如下所示:

      angular.module('appName').component('nameComponent', {
      template: `<ng-include src="'path_to_file/file-name.html'"></ng-include>`,
      

      我希望这会有所帮助!

      【讨论】:

        【解决方案4】:

        这里给出的大多数答案都涉及以某种方式预加载模板,以使其与指令同步。

        如果您想避免这样做 - 例如如果您有一个包含许多模板的大型 AngularJS 应用程序,并且您不想预先下载它们,您可以简单地将指令包装在同步加载的版本中。

        例如,如果你有一个名为 myDirective 的指令,它有一个异步加载的 templateUrl,你不想预先下载,你可以这样做:

        angular
          .module('my-module')
          .directive('myDirectiveWrapper', function() {
            return {
              restrict: 'E',
              template: "<my-directive></my-directive>",
            }
          });
        

        那么升级后的 Angular 指令只需要在对扩展 UpgradeComponentsuper() 调用中提供 'myDirectiveWrapper' 而不是 'myDirective'

        【讨论】:

          【解决方案5】:

          解决这个问题的一个技术含量很低的解决方案是将模板加载到 index.html 中,并为它们分配与指令正在寻找的 templateUrls 匹配的 ID,即:

          <script type="text/ng-template" id="some/file/path.html">
            <div>
              <p>Here's my template!</p>
            </div>
          </script>
          

          Angular 然后自动将模板放入 $templateCache 中,这是 UpgradeComponent 的 compileTemplate 开始寻找模板的地方,因此无需更改指令中的 templateUrl,一切都会正常工作,因为 id 与 templateUrl 匹配。

          如果您查看 UpgradeComponent 的源代码(见下文),您会看到处理获取 url 的注释掉的代码,因此它必须在工作中,但目前这可能是一个可行的解决方案,甚至一个可编写脚本的。

          private compileTemplate(directive: angular.IDirective): angular.ILinkFn {
              if (this.directive.template !== undefined) {
                return this.compileHtml(getOrCall(this.directive.template));
              } else if (this.directive.templateUrl) {
                const url = getOrCall(this.directive.templateUrl);
                const html = this.$templateCache.get(url) as string;
                if (html !== undefined) {
                  return this.compileHtml(html);
                } else {
                  throw new Error('loading directive templates asynchronously is not supported');
                  // return new Promise((resolve, reject) => {
                  //   this.$httpBackend('GET', url, null, (status: number, response: string) => {
                  //     if (status == 200) {
                  //       resolve(this.compileHtml(this.$templateCache.put(url, response)));
                  //     } else {
                  //       reject(`GET component template from '${url}' returned '${status}: ${response}'`);
                  //     }
                  //   });
                  // });
                }
              } else {
                throw new Error(`Directive '${this.name}' is not a component, it is missing template.`);
              }
            }
          

          【讨论】:

            【解决方案6】:

            作为一种解决方法,我使用 $templateCache 和 $templateRequest 将模板放入 $templateCache 以获取 Angular 所需的模板,在 AngularJS 上运行如下:

            app.run(['$templateCache', '$templateRequest', function($templateCache, $templateRequest) {
                    var templateUrlList = [
                        'app/modules/common/header.html',
                        ...
                    ];
                    templateUrlList.forEach(function (templateUrl) {
                        if ($templateCache.get(templateUrl) === undefined) {
                            $templateRequest(templateUrl)
                                .then(function (templateContent) {
                                    $templateCache.put(templateUrl, templateContent);
                                });
                        }
                    });
                }]);
            

            【讨论】:

              【解决方案7】:

              如果你不想修改你的 Webpack 配置,快速/肮脏的解决方案是使用 raw-loader 导入语法:

              template: require('!raw-loader!./your-template.html')

              【讨论】:

                【解决方案8】:

                我创建了一个方法实用程序来解决这个问题。 基本上它将模板 url 内容添加到 angular 的 templateCache, 使用 requireJS 和“text.js”:

                   initTemplateUrls(templateUrlList) {
                    app.run(function ($templateCache) {
                      templateUrlList.forEach(templateUrl => {
                        if ($templateCache.get(templateUrl) === undefined) {
                          $templateCache.put(templateUrl, 'temporaryValue');
                          require(['text!' + templateUrl],
                            function (templateContent) {
                              $templateCache.put(templateUrl, templateContent);
                            }
                          );
                        }
                      });
                    });
                

                你应该做的是把这个方法实用程序放在 appmodule.ts 中,然后创建一个你将要从你的 angular 指令升级的 templateUrls 列表,例如:

                const templateUrlList = [
                      '/app/@fingerprint@/common/directives/grid/pGrid.html',
                    ];
                

                【讨论】:

                  【解决方案9】:

                  我为此使用了 webpack 的 require.context:

                  templates-factory.js

                  import {resolve} from 'path';
                  
                  /**
                   * Wrap given context in AngularJS $templateCache
                   * @param ctx - A context module
                   * @param dir - module directory
                   * @returns {function(...*): void} - AngularJS Run function
                   */
                  export const templatesFactory = (ctx, dir, filename) => {
                      return $templateCache => ctx.keys().forEach(key => {
                  
                          const templateId = (() => {
                              switch (typeof filename) {
                                  case 'function':
                                      return resolve(dir, filename(key));
                                  case 'string':
                                      return resolve(dir, filename);
                                  default:
                                      return resolve(dir, key);
                              }
                          })();
                  
                          $templateCache.put(templateId, ctx(key));
                      });
                  };
                  

                  app.html-bundle.js

                     import {templatesFactory} from './templates-factory';
                  
                      const ctx = require.context('./', true, /\.html$/);
                  
                      export const AppHtmlBundle = angular.module('AppHtmlBundle', [])
                          .run(templatesFactory(ctx, __dirname))
                          .name;
                  

                  不要忘记将 html-loader 添加到您的 webpack.config.js

                   [{
                      test: /\.html$/,
                      use: {
                          loader: 'html-loader',
                          options: {
                              minimize: false,
                              root: path.resolve(__dirname, './src')
                          }
                      }
                  }]
                  

                  您可能还需要将相对路径转换为绝对路径。为此,我使用自己编写的 babel 插件 ng-template-url-absolutify

                  [{
                      test: /\.(es6|js)$/,
                      include: [path.resolve(__dirname, 'src')],
                      exclude: /node_modules/,
                      loader: 'babel-loader',
                      options: {
                          plugins: [
                              '@babel/plugin-syntax-dynamic-import',
                              ['ng-template-url-absolutify', {baseDir: path.resolve(__dirname, 'src'), baseUrl: ''}]
                          ],
                  
                          presets: [['@babel/preset-env', {'modules': false}]]
                      }
                  },
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 2017-05-06
                    • 2015-08-17
                    • 1970-01-01
                    • 2020-03-16
                    • 2016-05-17
                    • 1970-01-01
                    • 1970-01-01
                    • 2018-05-08
                    相关资源
                    最近更新 更多