在模板尝试渲染数据之前,如何确保数据已到达?

时间:2016-07-22 16:00:06

标签: angularjs angular redux immutable.js angular-cli

我用angular-cli,Immutable和Redux创建了一个应用程序。我按照this文章中的说明进行操作,该文章没有描述如何获取数据。我的应用程序需要使用来自异步http调用的数据初始化Redux存储。

我有一个用于列出具有模板的数据的组件。该组件从商店获取其数据,该数据取决于进行http调用的服务。 http调用有效,但应用程序抛出一个异常,指示列表组件在数据到达之前尝试获取数据。

我的存储库是here

该应用的演示是here

错误讯息:

main.js:21 TypeError: Cannot read property 'getState' of undefined
    at a.get [as objections] (https://dancancro.github.io/bernierebuttals/main.js:18:22268)
    at a._View_a0.detectChangesInternal (a.template.js:189:37)
    at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
    at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)
    at a._View_a_Host0.detectChangesInternal (a.template.js:34:8)
    at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
    at a.detectContentChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13588)
    at a.detectChangesInternal (https://dancancro.github.io/bernierebuttals/main.js:32:13345)
    at a.detectChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13138)
    at a.detectViewChildrenChanges (https://dancancro.github.io/bernierebuttals/main.js:32:13774)

以下是代码的一些相关部分:(我正在研究这个。存储库包含当前代码)

list.component.html

...

<ul id="objection-list" [sortablejs]="store.objections" [sortablejsOptions]="options" (update)="setTouched()">
<li *ngFor="let objection of store.objections">
   <list-objection 
   [objection]="objection" 
   [editable]="editable"
   (onEdit)="setTouched()" 
   (onReordered)="setReordered(objection)"
   ></list-objection>
</li>
</ul>

...

list.component.ts

import { Component, OnInit, ContentChildren, QueryList } from '@angular/core';
import { Router, ActivatedRoute } from '@angular/router';

import { SortablejsOptions, SORTABLEJS_DIRECTIVES } from 'angular-sortablejs';
import Immutable = require('immutable');

import { ObjectionComponent } from './objection/objection.component';
import { ObjectionModel } from '../objection';
import { ObjectionStore } from '../objection-store';
import { DataService } from '../data.service';
import { addObjection } from '../actions';


@Component({
  moduleId: module.id,
  selector: 'app-list',
  templateUrl: 'list.component.html',
  styleUrls: ['list.component.css'],
  providers: [ObjectionStore, DataService],
  directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
})
export class ListComponent implements OnInit {

  private sub: any;
  editable: boolean = false;
  touched: boolean = false;
  expanded: boolean = false;
  options: SortablejsOptions = {
    disabled: false
  };
  objectionID: number;

  constructor(
    private store: ObjectionStore,
    private route: ActivatedRoute) {}

  ...

}

异议-store.ts

import { Injectable } from '@angular/core';
import { ActivatedRoute } from '@angular/router';

import Immutable = require('immutable');
import { createStore } from 'redux';

import { ObjectionAction } from './actions';
import { reducer } from './reducer';
import { ObjectionModel } from './objection';
import { DataService } from './data.service';

@Injectable()
export class ObjectionStore {
  private sub: any;
  store: any;

  constructor(
    private dataService: DataService) {
           this.store = createStore(reducer, Immutable.List<ObjectionModel>(objections.json()));
    });

  }

data.service.ts

import { Injectable } from '@angular/core';
import { Http, Response, Headers } from '@angular/http';

import { Observable } from 'rxjs/Observable';
import { Subject } from 'rxjs/Subject';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/toPromise';

import { ObjectionModel } from './objection';
import { Area } from './area';

let objectionsPromise;

@Injectable()
export class DataService {
    result: Object;
    combined: any;
    error: Object;
    getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
    postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';

    static getObjection(objections: any[], id: number): ObjectionModel {
        return objections.filter(function(objection) {
            return objection.id === id
        })[0];
    }

    constructor(private http: Http) {
        objectionsPromise = this.http.get(this.getUrl).toPromise();
    }

2 个答案:

答案 0 :(得分:2)

基本答案

要回答当前的问题,从服务器获取异议并将它们放入模板可以像这样简单地完成:

  1. 使用async pipe将observable直接绑定到模板中。异步管道'unboxes'可观察(也承诺)并在模板更改时更新它们。
  2. <ul id="objection-list"
        [sortablejs]="store.objections"
        [sortablejsOptions]="options"
        (update)="setTouched()">
      <li *ngFor="let objection of objections | async">
        <list-objection 
                        [objection]="objection" 
                        [editable]="editable"
                        (onEdit)="setTouched()" 
                        (onReordered)="setReordered(objection)">
        </list-objection>
      </li>
    </ul>
    
    1. 初始化组件时,使用DataService初始化此observable:
    2. @Component({
        moduleId: module.id,
        selector: 'app-list',
        templateUrl: 'list.component.html',
        styleUrls: ['list.component.css'],
        providers: [ObjectionStore, DataService],
        directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
      })
      export class ListComponent implements OnInit {
      
        // ...
      
        private objections: Observable<ObjectionModel[]>;
      
        constructor(
          private dataService: DataService,
          private route: ActivatedRoute) {}
      
        ngOnInit() {
          this.objections = this.dataService.getObjections();
        }
      
        // ...
      
      1. 修复您的数据服务:
      2. import { Injectable } from '@angular/core';
        import { Http } from '@angular/http';
        
        import { Observable } from 'rxjs/Observable';
        import 'rxjs/add/operator/map';
        
        import { ObjectionModel } from './objection';
        
        @Injectable()
        export class DataService {
            result: Object;
            combined: any;
            error: Object;
            getUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec?json=1';
            postUrl: string = 'https://script.google.com/macros/s/AKfycbymzGKzgGkVo4kepy9zKIyDlxbnLbp-ivCvj8mVMClmWgr-V-g/exec';
        
            getObjections(private http: Http): Observable<ObjectionModel[]> {
                return this.http.get(this.getUrl) // returns an observable of the response
                    .map(response => response.json()); // transforms it into an observable of ObjectionModels
            }
        }
        

        有关Redux的说明

        请注意,这一切都是在没有Redux的情况下完成的。

        一般来说,我喜欢Redux并且我经常使用它。但是在你的例子中,你似乎做了几件非正统的事情:

        1. 您正在ObservableStore服务中创建商店 - 这告诉我您计划在您的应用中拥有多个商店。 Redux的主要原则之一是全局不可变状态,这意味着应用程序中通常只有一个Redux存储。

        2. 您似乎试图从服务器获取初始数据集,然后在响应返回时创建商店。将商店创建与这样的HTTP请求结合起来通常不是一个好主意。相反,我建议您在初始化应用程序时创建一个空存储,然后在HTTP请求返回时通过reducer更新它。

        3. 您可以在Angular 2中执行原始Redux,但是您可能会发现使用Angular的可观察量较大的API有点令人沮丧。幸运的是,人们(包括我)以面向观察者的redux库的形式为你完成了这项工作,例如ng2-reduxngrx/store

        4. 如果您使用ng2-redux,事情会更像是这样:

          顶级应用组件:构建商店并对其进行初始化:

          import { NgRedux } from 'ng2-redux';
          import { rootReducer } from './reducers';
          
          @Component({ /* ... */ })
          class App {
            constructor(private ngRedux: NgRedux<any>) {
              this.ngRedux.configureStore(rootReducer, {});
            }
          }
          

          列表组件:将模板绑定到商店当前数据的选择器。还会在初始化时触发数据提取。

          import { NgRedux, select } from 'ng2-redux';
          
          @Component({
            moduleId: module.id,
            selector: 'app-list',
            templateUrl: 'list.component.html',
            styleUrls: ['list.component.css'],
            providers: [DataService],
            directives: [ObjectionComponent, SORTABLEJS_DIRECTIVES]
          })
          export class ListComponent implements OnInit {
          
            // ...
          
            // Magic selector from ng2-redux that makes an observable out
            // of the 'objections' property of your store.
            @select('objections') objections: Observable<ObjectionModel[]>;
          
            constructor(
                private ngRedux: NgRedux<any>,
                private dataService: DataService) {}
          
            ngOnInit() {
                this.subscription = this.dataService.getObjections()
                   .subscribe(objections => this.ngRedux.dispatch({
                       type: FETCH_OBJECTIONS_OK,
                       payload: objections
                   },
                   error => this.ngRedux.dispatch({
                       type: FETCH_OBJECTIONS_ERROR,
                       error: error
                   });
                )
            }
          }
          

          好的......那么数据如何实际进入商店?通过减速机。请记住,在redux存储状态中,只能从reducer更改

          export function objectionReducer(state = [], action) {
              switch(action.type) {
                  case FETCH_OBJECTIONS_OK: return [ ...action.payload ];
                  case ADD_OBJECTION: return [ ...state, action.payload ];
                  // etc.
              }
          
              return state;
          }
          

          如果我们想要,我们也可以跟踪减速器中的错误,您希望如何构建这个错误取决于您。

          export function errorReducer(state = {}, action) {
              switch(action.type) {
                  case FETCH_OBJECTIONS_ERROR: return { objectionFetch: action.error }
              }
          }
          

          由于我们有一个商店,我们将模块化减速器并将它们组合在一起:

          import { combineReducers } from 'redux';
          import { objectionReducer } from './objection.reducer';
          import { errorReducer } from './error.reducer';
          
          export const rootReducer = combineReducers({
              objections: objectionReducer,
              error: errorReducer
          });
          

          更多学习/披露

          披露:我是Ng2-Redux的作者之一。然而,Ngrx / Store也是使用Ng2进行redux的一个很好的选择,虽然实现方式不同,但它与我上面描述的非常类似。

          我和我的同事们还在Angular2和Redux上保留了一些培训资源,我将在下面提供:

答案 1 :(得分:1)

您的数据服务有误。你错过了承诺/可观察的观点。 你应该阅读有关http客户端的角度文档。至少阅读这部分内容: https://angular.io/docs/ts/latest/guide/server-communication.html#!#promises

构造函数中没有http调用!  这更像是:

getHeroes (): Promise<Hero[]> {
  return this.http.get(this.heroesUrl)
                  .toPromise()
                  .catch(this.handleError);
}

在你习惯了之后,我真的建议你阅读一些关于Observables的内容。更清洁,更先进。