2

I have found some behaviour of an x3d inserted in an Angular (version 4) component which I think is really strange.

I have created an Angular project with the following structure:

x3d_and_angular/
    app/
        home/
            home.component.css
            home.component.html
            home.component.ts
            index.ts
        viewer/
            viewer.component.css
            viewer.component.html
            viewer.component.ts
            index.ts
        app.component.html
        app.component.ts
        app.module.ts
        app.routing.ts
        main.ts
    app.css
    index.html
    package.json
    red_box.x3d
    sysytemjs.config.js

I added the x3dom library to package.json and to sysytemjs.config.js.

My index.html looks like this:

<!DOCTYPE html>
<html>
<head>
    <base href="/" />
    <title>X3D and Angular Integration</title>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <!-- bootstrap css -->
    <link href="//netdna.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet" />
    <link rel='stylesheet' type='text/css' href='http://www.x3dom.org/download/x3dom.css'>

    <!-- application css -->
    <link href="app.css" rel="stylesheet" />

    <!-- polyfill(s) for older browsers -->
    <script src="node_modules/core-js/client/shim.min.js"></script>

    <script src="node_modules/zone.js/dist/zone.js"></script>
    <script src="node_modules/systemjs/dist/system.src.js"></script>

    <script src="systemjs.config.js"></script>
    <script>
        System.import('app').catch(function (err) { console.error(err); });
    </script>

    <script>
        function show_inline_url(){
            console.log(document.getElementById("inline_element"));
        }
    </script>
</head>
<body>
    <app>Loading ...</app>
</body>
</html>

The X3D file (red_box.x3d) showing the small red box that I want to render is:

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE X3D PUBLIC "ISO//Web3D//DTD X3D 3.3//EN" "http://www.web3d.org/specifications/x3d-3.3.dtd">
<x3d>
  <head>
    <meta name="title"/>
  </head>
  <Scene>
    <Shape id="shape_id">
        <appearance> 
            <material diffuseColor='1 0 0'></material>
        </appearance>       
        <box></box> 
    </Shape>
  </Scene>
</x3d>

The code of the home component is the following:

  • home.component.html:

    <div>
        <a [routerLink]="['/viewer']">X3D Viewer</a>
    </div>
    
  • home.component.ts:

    import { Component, OnInit } from '@angular/core';
    
    @Component({
        moduleId: module.id,
        templateUrl: 'home.component.html',
        styleUrls: ['home.component.css'],
    })
    
    export class HomeComponent{
        constructor() { }
    }
    

The problem arises in the viewer component. The code of the files which form this component is the following:

  • viewer.component.html:

    <div>
        <x3d width='600px' height='400px'> 
            <scene>
                <inline id="inline_element" [url]="x3d_filename"> </inline>
            </scene>
            <a [routerLink]="['/']"><span>BACK</span></a>
        </x3d> 
    </div>
    
  • viewer.component.ts:

    import { Component, OnInit } from '@angular/core';
    
    declare var System: any;
    
    @Component({
        moduleId: module.id,
        templateUrl: 'viewer.component.html',
        styleUrls: ['viewer.component.css'],
    })
    
    export class ViewerComponent implements OnInit{
    
        x3d_filename: string = "red_box.x3d";
    
        constructor() { 
            this.importX3d();
        }
    
        ngOnInit(){
            if(window["x3dom"] != undefined){
                window["x3dom"].reload();
            }
        }
    
        importX3d():void{        
            System.import('x3dom').then(() => { 
                console.log('loaded x3d');
            }).catch((e:any) => {
                console.warn(e);
            })
        }
    }
    

My Angular application goes from component home to component viewer, and viceversa, via routing. The routes are defined in the file app.routing.ts:

import { Routes, RouterModule } from '@angular/router';
import { HomeComponent } from './home/index';
import { ViewerComponent } from './viewer/index';

const appRoutes: Routes = [
    { path: '', component: HomeComponent},
    { path: 'viewer', component: ViewerComponent},

    // otherwise redirect to home
    { path: '**', redirectTo: '' }
];

export const routing = RouterModule.forRoot(appRoutes);

I noticed something really strange. Every time that I refresh the website, the first time that I access the component viewer, the scene is empty. However, the subsequent times that I access this component via routing (without refreshing), the box is rendered inside the scene.

However, if I replace the inline tag in the file viewer.component.html by <inline url="red_box.x3d"> </inline>, this does not happen (the box is rendered even the first time the website is refreshed).

Does anyone have any ideas on how could I fix this strange behaviour? I need to define the URL of the inline tag in the file viewer.component.ts.

I am very new to Angular and to web development, and I am getting mad about this. Any help would be appreciated, thanks.

GLR
  • 1,070
  • 1
  • 11
  • 29
  • 1
    Ah, I see your problem. It is a tiny Angular quirk. `inline` is not an Angular component, so you're not able to do direct data binding to the `url` property. Because you ignore **all** errors (in your ng module), you wouldn't know that it actually does tell you that in the console. A simple fix is to change `[url]` to `[attr.url]`, as `attr` is a property of a mockup component Angular creates for unknown elements. – Shane Jun 01 '17 at 21:16
  • Thank you so much, that fixed the problem. – GLR Jun 02 '17 at 07:03
  • I have found another thing that I do not understand. In the file viewer.component.html, if I add `*ngIf="true"` to the `div` element, the box is only rendered the first time I access the `viewer` component via routing. The subsequent times, nothing is shown (and the x3d scene is very small, as if the `width` and `height` attributes of the x3d file were not detected), until I refresh the website again. Any ideas of what am I doing wrong here? Thanks – GLR Jun 05 '17 at 14:18
  • 1
    Seems to me like it might be a race condition between loading the component content and loading the X3D library. Try to use `ngAfterViewInit` instead of `ngOnInit` to reload the library. I have never used X3D, I can't really help you with that. You can try to see if the `inline` element has something to do with it. Instead of using Angular, try to use a simple static html file to load a X3D scene with the inline element too see if it appears small again. If it does, it's not a problem with Angular. – Shane Jun 05 '17 at 14:49
  • It works fine now using `ngAfterViewInit`. If I set `*ngIf="x3d_filename"`, and if the value of the attribute `x3d_filename` is retrieved from an HTTP service: is it possible to create a custom event to trigger the reload of the `x3dom` library when the value of `x3d_filename` is retrieved? Thank you so much, I have no idea on how to implement this one, although I have reviewed the Angular documentation thoroughly. – GLR Jun 05 '17 at 15:52
  • I think that the problem could also be solved by "undoing" the import of the `x3dom` module (done with `System.import('x3dom')`) when I exit the `viewer` component. Is it possible to do this? – GLR Jun 05 '17 at 16:28
  • I solved the issue using `ngOnDestroy`. The code of this method is `System.delete(System.normalizeSync('x3dom'));`. Would you suggest a better way to do this? – GLR Jun 05 '17 at 16:43

1 Answers1

0

As Shane van den Bogaard suggested, the problem is solved by replacing [url] with [attr.url] in the file viewer.component.html. Also, it is necessary to modify the file viewer.component.ts. Its content now should be the following:

import { Component, AfterViewInit, OnDestroy } from '@angular/core';

declare var System: any;

@Component({
    moduleId: module.id,
    templateUrl: 'viewer.component.html',
    styleUrls: ['viewer.component.css'],
})

export class ViewerComponent implements AfterViewInit, OnDestroy{

    x3d_filename: string = "red_box.x3d";

    constructor() { 
        this.importX3d();
    }

    ngAfterViewInit(){
        if(window["x3dom"] != undefined){
            window["x3dom"].reload();
        }
    }

    importX3d():void{        
        System.import('x3dom').then(() => { 
            console.log('loaded x3d');
        }).catch((e:any) => {
            console.warn(e);
        })
    }

    ngOnDestroy(){
        System.delete(System.normalizeSync('x3dom'));
    }

}
GLR
  • 1,070
  • 1
  • 11
  • 29