Angular - 数据表:TypeError:无法读取 null 的属性“nodeName”

Posted

技术标签:

【中文标题】Angular - 数据表:TypeError:无法读取 null 的属性“nodeName”【英文标题】:Angular - Datatables: TypeError: Cannot read property 'nodeName' of null 【发布时间】:2017-11-19 01:46:42 【问题描述】:

这个有点棘手,我会尽力解释自己。

简要说明:

我在我的 Angular 项目中集成了 DataTables 库,自从我几个月前购买了该项目的主题以来就拥有了它。主题已更新,因此我继续使用最新版本更新我的项目。

奇怪的是,不起作用的是DataTables,而DataTables并没有改变!

代码分解:

我正在从 X 组件触发我的共享服务 IBOsService 的一个方法。

当这个方法被触发时,我的DatatableComponent 会在Promise 中导入数据表库。

在此之前从未遇到过问题。

DatatableComponent:

this.tablesService.initTableData$.subscribe(() => 
                if (!this.datatableInitialized) 
                    log.info('Starting script import promise');

                    Promise.all([
                        System.import('script-loader!my-plugins/datatables-bundle/datatables.min.js')
                    ]).then(values => 
                        log.data('success', JSON.stringify(values));
                        this.render();
                    , reason => 
                        log.error('error', JSON.stringify(reason));
                    );
                
            
        );

在我的控制台中,我看到:DataTables success []。因此,我明白承诺就是成功。

那么我们输入this.render();方法。

该方法一直持续到这里的这一行:

const _dataTable = element.DataTable(options);

问题是,在我的 IDE 中,我可以导航到所有变量、方法等...但我无法导航到 DataTable(),因此我猜只是无法识别此方法,这就是它引发错误的原因。

但是,由于脚本是在promise中加载的,IDE没有DataTables方法的映射是正常的……

完整的组件代码:

import  Component, Input, ElementRef, AfterContentInit, OnInit, Injectable, OnDestroy  from '@angular/core';
import  IBOsService  from '../../../+ibos/ibos.service';
import  Subscription  from 'rxjs/Subscription';
import  Log, Level  from 'ng2-logger';
import  logConfig  from '../../../../environments/log_config';

const log = Log.create('DataTables');
log.color = logConfig.dataTable;

declare let $: any;

@Component(

    selector: 'sa-datatable',
    template: `
        <table class="dataTable tableClass" >
            <ng-content></ng-content>
        </table>
    `,
    styles: [
        require('my-plugins/datatables-bundle/datatables.min.css')
    ]
)
@Injectable()
export class DatatableComponent implements OnInit, OnDestroy 

    @Input() public options: any;
    @Input() public filter: any;
    @Input() public detailsFormat: any;

    @Input() public paginationLength: boolean;
    @Input() public columnsHide: boolean;
    @Input() public tableClass: string;
    @Input() public width = '100%';

    public datatableInitialized: boolean;

    public subscription: Subscription;

    constructor(private el: ElementRef, private tablesService: IBOsService) 
        this.tablesService.refreshTable$.subscribe((tableParams) => 
                this.filterData(tableParams);
            
        );

        this.tablesService.initTableData$.subscribe(() => 
                if (!this.datatableInitialized) 
                    log.info('Starting script import promise');

                    Promise.all([
                        System.import('script-loader!my-plugins/datatables-bundle/datatables.min.js')
                    ]).then(values => 
                        log.data('success', JSON.stringify(values));
                        this.render();
                    , reason => 
                        log.error('error', JSON.stringify(reason));
                    );
                
            
        );
    

    ngOnInit() 
    

    render() 
        log.info('Starting render!');

        const element = $(this.el.nativeElement.children[0]);
        let options = this.options || ;

        log.info('1 render!');

        let toolbar = '';
        if (options.buttons) 
            toolbar += 'B';
        
        log.info('2 render!');

        if (this.paginationLength) 
            toolbar += 'l';
        

        if (this.columnsHide) 
            toolbar += 'C';
        

        log.info('3 render!');

        if (typeof options.ajax === 'string') 
            const url = options.ajax;
            options.ajax = 
                url: url,
                // complete: function (xhr) 
                //
                // 
            ;
        

        log.info('4 render!');

        options = $.extend(options, 

            'dom': '<\'dt-toolbar\'<\'col-xs-12 col-sm-6\'f><\'col-sm-6 col-xs-12 hidden-xs text-right\'' + toolbar + '>r>' +
            't' +
            '<\'dt-toolbar-footer\'<\'col-sm-6 col-xs-12 hidden-xs\'i><\'col-xs-12 col-sm-6\'p>>',
            oLanguage: 
                'sSearch': `<span class='input-group-addon'><i class='glyphicon glyphicon-search'></i></span>`,
                'sLengthMenu': '_MENU_'
            ,
            'autoWidth': false,
            retrieve: true,
            responsive: true,
            initComplete: (settings, json) => 
                element.parent()
                    .find('.input-sm')
                    .removeClass('input-sm')
                    .addClass('input-md');
            
        );

        log.info('5 render! element', JSON.stringify(element));
        log.info('5.1 render! options', JSON.stringify(options));

        const _dataTable = element.DataTable(options);

        log.info('5.2 render! _dataTable', JSON.stringify(_dataTable));

        if (this.filter) 
            // Apply the filter
            element.on('keyup change', 'thead th input[type=text]', function () 
                console.log('searching?');
                _dataTable
                    .column($(this).parent().index() + ':visible')
                    .search(this.value)
                    .draw();
            );
        

        log.info('6 render!');

        if (!toolbar) 
            element.parent().find('.dt-toolbar')
                .append(
                    '<div class="text-right">' +
                    '<img src="assets/img/logo.png"  style="width: 111px; margin-top: 3px; margin-right: 10px;">' +
                    '</div>'
                );
        

        log.info('7 render!');

        if (this.detailsFormat) 
            const format = this.detailsFormat;
            element.on('click', 'td.details-control', function () 
                const tr = $(this).closest('tr');
                const row = _dataTable.row(tr);
                if (row.child.isShown()) 
                    row.child.hide();
                    tr.removeClass('shown');
                 else 
                    row.child(format(row.data())).show();
                    tr.addClass('shown');
                
            );
        

        log.info('8 render!');

        this.datatableInitialized = true;
    

    filterData(tableParams) 
        console.log('reloading DT... With these parameters: ' + JSON.stringify(tableParams));

        const element = $(this.el.nativeElement.children[0]);
        const table = element.find('table.dataTable');

        log.data('current table element is: ', JSON.stringify(table));

        Object.keys(tableParams).forEach(function (key) 
            log.warn('current key: ', JSON.stringify(key));
            table.DataTable().column(`$key:name`).visible(tableParams[key]);
        );

        table.DataTable().ajax.reload();
    

    ngOnDestroy() 
        if (this.subscription) 
            this.subscription.unsubscribe();
        
    

你会看到我已经用日志侵入了组件,它帮助我了解了我的组件失败的地方。

完整的控制台日志:

有趣的信息:

如果我调用filterData(tableParams) 方法... DataTable 呈现没有问题。

这告诉我两件事:

    DataTable() 不是问题。 问题只是在第一次渲染时,然后,在更新时,我没有遇到任何问题。

我非常抱歉我的文字墙...但是我自己做了一些福尔摩斯,但找不到解决方案。

如果您需要澄清或更多细节,请告诉我。

提前致谢!

更新:

经过几天的调试,我发现了一个可能的错误来源:

问题是snull

也许在 DataTables 方面更有经验的人或遇到此问题的人可以给我一个关于正在发生的事情的提示。

我会在这里调试... ;)

更新 2:

经过更多调试,我发现 DataTables 没有进行第一次 Ajax 调用

我正在使用服务器端 DataTables,并在 POST 中调用 Ajax。

这是我的options对象的代码:

this.options = 
    dom: 'Bfrtip',
    processing: true,
    serverSide: true,
    pageLength: 20,
    searchDelay: 1200,
    ajax: 
        url: this.jsonApiService.buildURL('/test_getUsers.php', 'remote'),
        type: 'POST',
        data: function (d) 
            Object.assign(d, IBOsTable.params);
            log.data('DT options obj. New params are: ', JSON.stringify(IBOsTable.params));
            return d;
        
    ,
    columns: this.initColumns,
;

不是在初始化时进行 Ajax 调用,而是在 table.DataTable().ajax.reload(); 上进行调用。

这告诉我代码没有错误或损坏(reload() 的作用就像一个魅力)...

我仍然没有完全理解为什么这个 DataTables 的初始化不起作用......但我相信我已经足够接近了!

【问题讨论】:

s在哪里定义?我没有看到那个代码 @Bindrid 嗨! s 不是决定因素。我发现我的 DT 没有进行第一次 Ajax 调用,因此没有要呈现的实际数据,这就是发生错误的原因。这里的问题是找出为什么不进行第一次 Ajax 调用。干杯! ;) 【参考方案1】:

我终于找到了错误的根源!

如果您阅读我的更新,您会发现我发现我的 DataTables 没有进行第一次 Ajax 调用,但在 reload() 上运行良好:

这是因为初始化中的 javascript 错误不允许进行 Ajax 调用。提示:TypeError:无法读取 null 的属性“nodeName”

在speaking to Allan (from DataTables)之后,他给了我提示,错误可能是因为thead的列数不等于tbody的列数。

所以我检查了我的 .html,发现我有一些空的 &lt;thead&gt;&lt;tfoot&gt; 标签。我评论了他们......并且工作了!

我已经反复尝试了所有可能的解决方案:

依赖关系 Angular 2 应用程序中的 jQuery 库 实例化 DataTable 服务器端的多种不同方式 更改代码方法:TypeScript、jQuery、Ng2 等...

最后我只需要清除加载我的 DataTable 的 div 以避免这个问题。

有趣的事实: 有了这个 .html,我的应用程序运行了 3/4 个月没有任何问题,到目前为止,DataTables 没有任何问题......

【讨论】:

我的页脚中也有一个空的 但在我的情况下,当我将 dt-responsive 类添加到我的数据表时,错误才出现

以上是关于Angular - 数据表:TypeError:无法读取 null 的属性“nodeName”的主要内容,如果未能解决你的问题,请参考以下文章

在 Angular => 错误中显示数据列表时出现此错误:TypeError:无法读取未定义的属性“0”

Angular.js $http.post TypeError:无法读取未定义的属性“数据”

Angular 6:TypeError:无法读取未定义的属性“值”

Angular:错误:未捕获(承诺):TypeError:Object(...)不是函数

Angular TypeError:无法读取未定义的属性“名称”

Angular给出TypeError:“无法读取未定义的属性'id'”