Skip to content
This repository was archived by the owner on Feb 22, 2018. It is now read-only.

No getter for ... error, when working with dynamic expressions and angular transformer #1137

Closed
michalpie opened this issue Jun 10, 2014 · 8 comments

Comments

@michalpie
Copy link

In our application, we often use Scope.eval() to evaluate expressions which are not known at compilation time (they are retrieved from server in AJAX). Likewise, we often generate HTML templates in memory, based on AJAX data, which contain expressions not known at compilation time. In all these cases after compilation using angular transformer, we get "No getter for ..." error message.
@MirrorsUsed and override: '*' stopped working, so we now have completely no way to minify the javascript code.

AngularDart: 0.12.0
Dart 1.4.2

@mvuksano
Copy link
Contributor

Would it be possible to see an example of the problem (i.e. some code snippet)?

@adarshaj
Copy link

This very same problem exists in dart-ng-infinite-scroll, I had filed a similar issue on infinite-scroll. Please read the description on issue for complete explanation.

Relevant source code lines are L17 and L62 of ng_infinite_scroll.dart

@michalpie
Copy link
Author

Thank you, @adarshaj, but I'm afraid the proposed solution won't work in our case. The thing is that the expression we evaluate is just not known at compilation time, but is retrieved later in an AJAX call.

Here is an extremely simplified demonstration code of what we do. When run in Dartium, works well, but when compiled using angular transformer, I receive " No getter for 'categories'." error message.

index.html

<!DOCTYPE html>
<html ng-app>
<head>
    <title>Chapter Six - A Simple Recipe Book</title>
    <script type="text/javascript" src="web/packages/web_components/platform.concat.js?"></script>
    <script type="text/javascript" src="web/packages/web_components/dart_support.js"></script>
    <script type="application/dart" src="recipes.dart"></script>
    <script type="text/javascript" src="web/packages/browser/dart.js"></script>    
</head>
<body>
  <h1>Recipes</h1>
  <my-table></my-table>
</body>
</html>

recipes.dart

import 'package:angular/angular.dart';
import 'package:angular/application_factory.dart';
import 'package:di/di.dart';
import 'dart:html';
import 'dart:convert';


class MyAppModule extends Module {
  MyAppModule() {   
    bind(MyTable);
    bind(QueryService);
    bind(ViewTemplate);
  }
}

@Injectable()
class QueryService { 
  // get data for the table (normally from a server)
  Map getTableData() {
    return JSON.decode('''
      {
        "title" : "Recipe Book",
        "categories" : [
           {
             "name" : "lunch",             
             "recipes" : [ { 
                  "name" : "lunch 1",
                  "amount" : 4,
                  "amountUnits" : "portions",
                  "rating" : 5,
                  "preparation" : 10,
                  "preparationUnits" : "min."
                }, { 
                  "name" : "lunch 2",
                  "amount" : 600,
                  "amountUnits" : "mL",
                  "rating" : 3,
                  "preparation" : 1,
                  "preparationUnits" : "hour"
                }
             ]
           }
        ]
      }''');
  }

  // get configuration for the table (normally from a server)
  Map getTableConfig() {
      return JSON.decode('''
        {
          "rowsExpression" : "categories[0].recipes",         
          "cols" : [ {
               "title" : "name",             
               "cellFormat" : "{{name}}"
             }, {
               "title" : "amount",             
               "cellFormat" : "{{amount}} {{amountUnits}}"
             }, {
               "title" : "rating",             
               "cellFormat" : "{{rating}}"
             }, {
               "title" : "preparation",             
               "cellFormat" : "{{preparation}} {{preparationUnits}}"
             }
          ]
        }''');
    }
}


@Component(
    selector: 'my-table', 
    publishAs: 'ctrl', 
    template: '''<table>
           <tr><td ng-repeat="col in ctrl.cols">{{col.title}}</td></tr>  
           <tr ng-repeat="row in ctrl.rows">
               <td ng-repeat="col in ctrl.cols"><view-template template="{{col.cellFormat}}" 
                       data="row"></view-template></td>
           </tr>
          </table>''')
class MyTable {
  QueryService queryService;  
  Scope scope;
  List rows;
  List cols;
  MyTable(this.queryService, this.scope) {
    var cfg = queryService.getTableConfig();
    var rowsExpression = cfg['rowsExpression'];
    rows = scope.eval(rowsExpression, queryService.getTableData());
    cols = cfg['cols'];
  }

}


@Component(
    selector: 'view-template', 
    publishAs: 'ctrl',
    map: const {
        'template' : '@template',
        'data': '=>data'
       }    
)
class ViewTemplate implements ShadowRootAware {  

  final Injector _injector;
  final Scope _scope;
  final DirectiveMap _directiveMap;
  final ViewCache _viewCache;

  bool _viewUpdated = false;
  ShadowRoot _shadowRoot;

  String _template;
  Map _data;

  ViewTemplate(this._injector, this._scope, this._directiveMap, this._viewCache);

  String get template => _template;

  set template(String value) {
    _template = value;
    _updateView();
  }

  Map get data => _data;

  set data(Map value) {
    _data = value;
    _updateView();
  }


  void onShadowRoot(ShadowRoot shadowRoot) {
    _shadowRoot = shadowRoot;
    _updateView();
  }

  void _updateView() {
    if(_viewUpdated) {
      return;
    }
    if(_template==null || _data==null || _shadowRoot==null) {
      return;
    }
    Scope childScope = _scope.createChild(_data);
    Injector childInjector = _injector.createChild([new Module()..bind(Scope, toValue: childScope)]);    
    ViewFactory viewFactory = _viewCache.fromHtml(_template, _directiveMap);
    var view = viewFactory(childInjector);
    _shadowRoot.nodes.addAll(view.nodes);
    _viewUpdated = true;
  }
}

void main() {
  applicationFactory()
      .addModule(new MyAppModule())
      .run();
}

@mvuksano
Copy link
Contributor

@michalpie Do you know the expressions ahead of time (and you just bring them later to the client) or you are unable to tell which expressions will be used at runtime ahead of time?

@michalpie
Copy link
Author

I am unable to tell ahead of time what expressions will be used and how the data will look like.

@vicb
Copy link
Contributor

vicb commented Jun 13, 2014

I think you need getters because for now we do not differentiate when you are accessing an object or an array. ie categories[0].recipes access <scope>.categories[0].recipes.

We require getters for accesing object properties.

I think this might be solved by #1040 after which you will have to use "[]" to access Map / List.

Meanwhile, could you please try to replace your expressions:

  • categories[0].recipes -> this["categories"][0]["recipes"]
  • name -> this["name"], ...

@michalpie
Copy link
Author

Thank you, Victor, the solution with map syntax works fine.

@vicb
Copy link
Contributor

vicb commented Jun 13, 2014

Could you please close the issue if there is no more problem.
It will be easier/clearer in the future where using "." for accessing maps / arrays will be removed.

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

No branches or pull requests

4 participants