Angular 2 - 为什么我需要zone.run()?

时间:2016-05-10 20:55:33

标签: typescript angular

我正在尝试在Angular 2中创建一个显示服务数据的组件。该服务基本上从用户输入一些json文件后加载一些数据。我一直试图让组件更新,但它似乎没有认识到更改,除非我从我的服务提交事件后调用zone.run()。组件的代码如下......

@Component({
    selector: 'assess-asset-group',
    directives: [AssetComponent, AssetHeaderComponent, NgFor, NgIf],
    template: `

            <div *ngIf="assetService.schema != null">
                <div class="asset-group" *ngFor="#assetTypeName of assetService.schema.assetTypeNames"> 
                    <div class="asset-type-title"><span>{{assetService.schema.assetTypes[assetTypeName].name}}s</span></div> 
                    <table class="asset-group-table" cellpadding=0 cellspacing=0>
                        <thead>
                            <tr assess-asset-header [assetType]="assetService.schema.assetTypes[assetTypeName]"></tr>
                        </thead>
                        <tbody>
                            <tr assess-asset *ngFor="#asset of assetService.assetsForType(assetTypeName)" [asset]="asset"></tr>
                        </tbody>
                    </table>
                    <button class="new-asset-btn" (click)="assetService.addAsset(assetTypeName)">New</button>
                </div>
            </div>`,
    providers: [provide(AssetService, {useValue: injector.get(AssetService)})]
})
export class AssetGroupComponent {

    public assetService: AssetService;
    public zone: NgZone;

    constructor( @Inject(AssetService) assetService: AssetService, zone: NgZone) {
        this.assetService = assetService;
        this.zone = zone;
    }

    ngOnInit() {
        this.assetService.proejectLoadedEmitter.subscribe((e) => { this.zone.run(() => { }) });
    }

    ngOnDestroy() {
        this.assetService.proejectLoadedEmitter.unsubscribe();
    }
}

我做错了什么或者这是我需要做什么才能更新视图?

UPDATE - AssetService Class

@Injectable()
export class AssetService{
    public assets: Assets.Asset[] = [];
    public assetTypeDefinitions: any = null;

    public schema: Schema = null;
    public assetsAsObj: any = null; // Asset file loaded as object

    @Output() proejectLoadedEmitter: EventEmitter<any> = new EventEmitter();

    constructor(){
    }

    public loadProject(config: Project){
        // Load schema
        // populate AssetTypeDefinitions as object keyed by type
        let data = fs.readFileSync(config.schemaPath, 'utf8');
        if (!data) {
            utils.logError("Error reading schema file");
            return;
        }
        let struc = fs.readFileSync(config.structurePath, 'utf8');
        if (!struc) {
            utils.logError("Error reading structure file");
            return;
        }

        this.schema = new Schema(JSON.parse(data), struc);
        this.readAssets(config.assetFilePath);
    }

    /**
     * @brief Adds a new asset to the assets array 
     * @details Constructs the asset based on the type and populates
     *  its fields with appropreiate default values
     * 
     * @param type The type of the asset - specified in the schema
     */
    public addAsset(type: string): void {
        // Need to make sure there is a loaded type definition for the specified type
        if(!this.schema.assetTypes.hasOwnProperty(type)){
            utils.logError("Error occured during call to addAsset - type \"" + type + "\" is not specified in the loaded schema");
            return;
        }
        // Creeate a new asset object - passing in the type definition from the schema
        this.assets.push(new Assets.Asset(this.schema.assetTypes[type]));
    }   

    /**
     * Write the current assets to a file using the specified format
     * If the outputPasth isn't specied try and load it from the project.json file
     */
    public writeAssets(format:AssetWriteFormat, outputPath?: string) : void {

        var outStructureStr = this.schema.structureStr;
        // insert AS properties from schema into output assets
        this.schema.properties.forEach(prop => {
            outStructureStr = outStructureStr.replace(new RegExp('"' + prop +'"', 'i'), this.retriveValueForSchemaProperty(prop));
        });

        fs.writeFileSync("C:/Projects/Assess/assets.json", outStructureStr);
    }

    public readAssets(inputPath?: string) : void{
        let assetsStr = fs.readFileSync(inputPath, 'utf8');

        let strucToAssetMap = {};
        let strucObj = JSON.parse(this.schema.structureStr);
        this.schema.properties.forEach(p => {
            strucToAssetMap[p] = this.findValueInObject(strucObj, p).reverse();
        });

        // @TODO Load custom properties
        let assetsObj = JSON.parse(assetsStr);
        var c = null;
        strucToAssetMap["AS_ASSETS"].forEach(p => {
            if(c == null){
                c = assetsObj[p];
            }else{
                c = c[p];
            }
        });
        c.forEach((asset) => {
            let a:Assets.Asset = new Assets.Asset(this.schema.assetTypes[asset.type], asset);
            this.assets.push(a);
        });
        console.log(this.assets);
        this.proejectLoadedEmitter.emit(null);
    }

    public assetsForType(type:string): Assets.Asset[]{
        var ret: Assets.Asset[] = [];
        for(let idx in this.assets){
            if(this.assets[idx].definition.type === type){
                ret.push(this.assets[idx]);
            }
        }
        return ret;
    }

    public retriveValueForSchemaProperty(property: string) : string{
        if(AS_SchemaTypes.indexOf(property) != -1){
            switch (property) {
                case "AS_ASSETS":
                    let outAssets = [];
                    this.assets.forEach((asset) => {
                        let outAsset = {};
                        outAsset["type"] = asset.definition.type;

                        for (let key in asset.fields) {
                            outAsset[key] = asset.fields[key].value;
                        }
                        outAssets.push(outAsset);
                    });
                    return JSON.stringify(outAssets, null, "\t");
            }
        }else{
            // @TODO Retrive custom properties
            return '"DDDDDD"';
        }
        return "";
    }

    public findValueInObject(obj: any, property: string, path: any[] = []): any[] {
        for(let x in obj){;
            let val = obj[x];
            if (val == property){
                path.push(x);
                return path;
            }
            else if(val != null && typeof val == 'object'){
                let v = this.findValueInObject(val, property, path);
                if(v != null){
                    path.push(x);   
                    return path;
                }
            }
        }
        return null;
    }
}

2 个答案:

答案 0 :(得分:12)

这需要了解您正在使用的:NERDTree的内部运作方式。

Angular在其区域内运行组件的代码,其中大多数异步API(AssetServiceaddEventListener,...)都被修补,因此区域可以在发生此类异步回调时通知Angular。这是Angular运行变化检测的时候。

如果您在Angular之外初始化setTimeout或通过其他方式AssetService执行Angulars区域之外的代码,则Angular不会收到有关发生的异步回调的通知,并且不会运行更改检测

使用AssetService,您明确地在Angulars区域内执行代码,然后运行更改检测。

答案 1 :(得分:0)

是否可能是由于在您的视图中引用assetService,参数而不是this.assetService引起的?也许这会导致Angular的变化检测在没有调用zone.run()的情况下被触发。