具有外部数据和TypeScript的Ng网格:编译错误“无法设置未定义的属性'gridDim'”

时间:2013-07-25 17:34:06

标签: asp.net-mvc-4 angularjs asp.net-web-api ng-grid

更新#1:在我评论的修复之后,现在我的应用程序启动,但网格未呈现,除了其边界框和过滤器按钮和弹出窗口。然而,我从控制台得到没有错误,并且只要我可以通过调试器到达,我可以看到从服务器获得的数据是正常的。如果我使用Batarang,我可以看到与我的模型相对应的范围,正确填充项目。我相应地更新了可下载的repro解决方案。任何人都可以解释为什么ng-grid不在这里更新?


我开始使用ng-grid和TypeScript,一旦我的测试应用程序启动,我就会发现问题。请参阅本文的底部,以获取完整测试解决方案的链接。当然,即使在这几个文件中我也犯了很多错误,但我想先从一些事情开始,逐步学习。

MVC应用程序有两个客户端应用程序:

  • app.js用于默认视图(主页/索引)。这里没有打字稿,整个代码在这个单独的文件中是自包含的。该代码源自ng-grid文档中的分页示例,并尽可能保持最简单。

  • MyApp.js用于另一个视图中的更逼真的样本(Home / Model)。此示例使用服务,模型和控制器,其JS代码是从TypeScript编译的。为了简单起见,我只是将这些组件存储在Scripts / App下,存储在控制器,模型和服务的文件夹中,每个文件只包含一个类或接口。生成的JS文件手动包含在视图中。

app.js有效,但它有过滤问题。我在这里发布了这些: Server-side filtering with ng-grid: binding issue?

MyApp.js有ng-grid的启动问题。应用程序启动后,网格绑定中会抛出一个TypeError:

TypeError: Cannot set property 'gridDim' of undefined
    at ngGridDirectives.directive.ngGridDirective.compile.pre (http://localhost:55203/Scripts/ng-grid-2.0.7.js:2708:37)
    at nodeLinkFn (http://localhost:55203/Scripts/angular.js:4392:13)
    at compositeLinkFn (http://localhost:55203/Scripts/angular.js:4015:15)
    at nodeLinkFn (http://localhost:55203/Scripts/angular.js:4400:24)
    at compositeLinkFn (http://localhost:55203/Scripts/angular.js:4015:15)
    at publicLinkFn (http://localhost:55203/Scripts/angular.js:3920:30)
    at resumeBootstrapInternal (http://localhost:55203/Scripts/angular.js:983:27)
    at Object.$get.Scope.$eval (http://localhost:55203/Scripts/angular.js:8057:28)
    at Object.$get.Scope.$apply (http://localhost:55203/Scripts/angular.js:8137:23)
    at resumeBootstrapInternal (http://localhost:55203/Scripts/angular.js:981:15) <div ng-grid="gridOptions" style="height: 400px" class="ng-scope"> angular.js:5754

我通过Google搜索找到的唯一类似问题是https://github.com/angular-ui/ng-grid/issues/60,但它似乎与我的情况无关,因为网格选项的设置太晚了。

服务器端只有一个API RESTful控制器,返回服务器分页,已排序和过滤的项目。

你可以在这里找到完整的repro解决方案(只需保存,解压缩并打开;所有依赖项都来自NuGet);有关更多信息,请参阅readme.txt文件:

http://sdrv.ms/167gv0F

启动应用程序并单击右上角的MODEL以运行引发错误的TypeScript应用程序。整个应用程序由1个控制器,1个服务和1个模型组成。

对于像我这样的初学者来说,拥有像这样的简单工作示例会很不错。有人可以帮忙吗?

5 个答案:

答案 0 :(得分:6)

此错误表示Angular尝试解析gridOptions时尚未定义ng-grid="yourArray",其中yourArray是提供给gridOptions的相同数组。重构之前正在运行的ng-grid后,我遇到了同样的问题。

所以{<1}}必须在之前定义应用了gridOptions属性的元素(而不是在该元素自己的控制器中)。

我通过在某个外部元素中定义ng-grid="yourArray"来解决这个问题(例如,在全局/应用范围内)。

P.S。也许有更好的方法,但这对我有用。

答案 1 :(得分:3)

您要向网格添加数据的位置?

如果您在成功通话中写 $ scope.myGrid = {data:“someObj”}; ,那么它将无效。

请参阅以下原因:(列于https://github.com/angular-ui/ng-grid/issues/60

You can't define the grid options in the success call. You need to define
them on the scope in your controller and then set the data or column
definitions, etc... from the success call.

答案 2 :(得分:0)

你需要做什么?,首先要看看如果你的查询或数据访问如何使你的项目和revizar,通过服务的光束,如果是这样我必须添加管理路线app的文件,客户端。

仍然如此。

'use strict';

angular.module('iseApp', [
        'ngCookies',
        'ngResource',
        'ngSanitize',
        'ngRoute',
        **'ngGrid',**
        'campaignServices',
        'dialinglistServices',
        'itemServices'
    ])
    .config(function ($routeProvider, $locationProvider, $httpProvider) {
        $routeProvider

答案 3 :(得分:0)

当您在指令中添加ng-grid时,必须确保在尝试解析html之前加载了网格选项。

您可以在链接函数中设置一个布尔值:

scope.isDirectiveLoaded=true;

然后,在您的模板中,使用ng-if:

<div ng-if="isDirectiveLoaded">
    <div ng-grid="myGrid"/>
</div>

答案 4 :(得分:0)

我遇到了同样的问题,渲染了空白网格。 我最终的解决方法是在组件的控制器构造函数中设置this.gridOptions。在选项中,像这样用$ ctrl引用所有内容。因此,数据引用$ ctrl.gridData。在我的组件控制器中将gridData指定为属性。 $ ctrl未定义为属性。 这是在加载数据之前在构造函数中完成的。 this.gridData在构造函数中定义之后,然后在另一个函数中填充。这些选项是首先定义的,我认为从我读过的一些东西来看这很重要。

对于事件挂钩,请传递null而不是$ scope。

    import { Directive, Input, HostListener, OnInit, OnDestroy, ElementRef } from '@angular/core';

    import {SelectionModel, SelectionChange} from '@angular/cdk/collections';

    import { Subject, BehaviorSubject, Observable, merge, pipe, } from 'rxjs';
    import { shareReplay, takeUntil, withLatestFrom, tap } from 'rxjs/operators';

    import {MatTable, MatTableDataSource} from '@angular/material/table';

    /**
     * Directive that adds shift-click selection to your mat-table.
     * It needs the datasource from the host, the selectionModel of the checkboxes 
     * and optionally an array of [ids] to find the correct rows in the
     * (possibly sorted/ filtered) source array.
     */

    @Directive({
      selector: '[shiftClickSource]'
    })
    export class ShiftClickDirective implements OnInit, OnDestroy {

      finishHim = new Subject<void>();

      lastItem: any;
      lastShiftSelection: any[];

      alwaysTheRightSourceArray$: Observable<any>;
      // the always right source array :-)
      source: any[];
      shiftHolding$ = new BehaviorSubject<boolean>(false);
      //click that happens on tbody of mat-table
      click$ = new Subject<void>();
      // observable to record the last change of the checkbox selectionModel
      recordSelectionChange$: Observable<SelectionChange<any>>;

      @HostListener('document:keydown.shift', ['$event'])
      shiftDown(_) {
        this.shiftHolding$.next(true);
      }
      @HostListener('document:keyup.shift', ['$event'])
      shiftUp(event: KeyboardEvent) {
        this.shiftHolding$.next(false);
      }

      // datasource that is used on the Checkbox-Table
      @Input('shiftClickSource') dataSource: MatTableDataSource<any>;
      // Is optional, if id col is known this will improve performance.
      @Input('shiftClickSourceId') idArr: string[];
      @Input('shiftClickSelectModel') selection: SelectionModel<any>;

      constructor(private host: ElementRef) {}

      ngOnInit() {

        // data can change order (sorting) and can be reduced (filtering)
        this.alwaysTheRightSourceArray$ = this.dataSource.connect()
        .pipe(
          tap(_ => {
            this.source = this.dataSource
            .sortData(
              this.dataSource.filteredData,
              this.dataSource.sort
            );
          })
        );

        // lets record the last change of 
        this.recordSelectionChange$ = this.selection.changed.pipe(
          shareReplay(1)
        )

        // clicks on tbody mean that we need to do something
        this.host.nativeElement.childNodes[1].addEventListener("click", function() {
          this.click$.next();
        }.bind(this));

        const reactOnClickOnTbody$ = 
        this.click$.pipe(
          withLatestFrom(this.shiftHolding$.asObservable()),
          withLatestFrom(this.recordSelectionChange$, (arr, c): [SelectionChange<any>, boolean ] => [c, arr[1]] ),
          tap( arr => {
                const v = arr[0];
                const sh = arr[1];
                const ans = [...v.added, ...v.removed][0];
                if ( sh && this.lastItem ) {
                  this.onTbodyClicked(this.lastItem, ans);
                } else {
                  this.lastItem = ans;
                  this.lastShiftSelection = undefined;
                  // console.log('clear');
                }
          }),
        );

        merge(
          reactOnClickOnTbody$,
          this.alwaysTheRightSourceArray$
        ).pipe(takeUntil(this.finishHim.asObservable()))
        .subscribe();
      }

      // This function does all the real work.
      onTbodyClicked(from: object, to: object) {

        // console.log('onTbodyClickedRuns');
        const fIdx = this.getIndexBasedOnData(from);
        const tIdx = this.getIndexBasedOnData(to);

        let ans;
        let ans_last;
        if( fIdx > tIdx ) {
          ans  = this.source.slice(tIdx, fIdx);
        } else {
          // console.log('seconds index case');
          ans  = this.source.slice(fIdx +1 , tIdx + 1);

        }

        if (this.lastShiftSelection) {
          this.selection['_emitChanges']=false;
          this.selection.deselect(...this.lastShiftSelection);
          this.selection['_emitChanges']=true;
        }

        this.lastShiftSelection = [...ans];

        if( fIdx > tIdx ) {
          ans_last = ans.shift();
        } else {
          ans_last = ans.pop();      
        }
        // console.log(this.lastShiftSelection);

        const cond =  ans.every(el => this.selection.isSelected(el)) && !this.selection.isSelected(ans_last)

        if ( cond ) {
            // console.log('deselect')
            this.selection['_emitChanges']=false;
            this.selection.deselect(...ans);
            this.selection['_emitChanges']=true;
        } else {
            // console.log('select')
            this.selection['_emitChanges']=false;
            this.selection.select(...ans, ans_last);
            this.selection['_emitChanges']=true;
        }    

      }

      // helper function
      private getIndexBasedOnData(row, source = this.source): number {

        let ind: number;
        if (this.idArr) {
          ind = source.findIndex( _d => this.idArr.every(id => _d[id] === row[id] ));
        } else {
          ind = source.findIndex( _d => Object.keys(row).every(k => _d[k] === row[k] ));
        }

        return ind;
      }

      ngOnDestroy() {
        this.finishHim.next();
        this.finishHim.complete();
      }

    }

在行模板中,我引用了组件中定义的函数。在转换为组件之前,我引用了以下函数:

this.gridOptions = {
    enableGridMenu: true,
    minRowsToShow: 25,
    rowHeight: 36,
    enableRowHashing: true,
    data: '$ctrl.gridData',
    rowTemplate: this.$rootScope.blockedRowTemplate,
    onRegisterApi: ($ctrl) => {
        this.gridApi = $ctrl;
        this.gridApi.colMovable.on.columnPositionChanged(null, (colDef, originalPosition, newPosition) => {
            this.saveState();
        });

        this.gridApi.colResizable.on.columnSizeChanged(null, (colDef, deltaChange) => {
            this.saveState();
        });

        this.gridApi.core.on.columnVisibilityChanged(null, (column) => {
            this.saveState();
        });

        this.gridApi.core.on.sortChanged(null, (grid, sortColumns) => {
            this.saveState();
        });
        this.gridApi.core.on.filterChanged(null, (grid, sortColumns) => {
            this.saveState();
        });
    }
};

转换为组件后,我需要在函数名称之前添加$ ctrl

ng-click="grid.appScope.jumpToExport(row.entity);" 

这就是在html中引用该组件的方式

ng-click="grid.appScope.$ctrl.jumpToExport(row.entity);"