0
votes

Something related to ModalDialogService is preventing some things on the component being rendered in the modal to not work correctly.

I can bind tap events to function on the component, as is done in the example, and can close the modal from a button within it.

But if I bind a label to a property on the component, nothing display. Anywhere else in my app the same code displays.

I suspect something to do with having to use entryComponents to register the component, but can't figure out what is going on.

import { NgModule, ModuleWithProviders } from '@angular/core';
import { CommonModule } from '@angular/common';

/* Nativescript modules */
import { NativeScriptFormsModule } from 'nativescript-angular/forms';
import { NativeScriptModule } from 'nativescript-angular/nativescript.module';
import { NativeScriptRouterModule } from 'nativescript-angular/router';
import { ModalDialogService } from 'nativescript-angular/modal-dialog';
import { registerElement } from 'nativescript-angular/element-registry';
registerElement('CardView', () => require('nativescript-cardview').CardView);
/* End nativescript modules */

import { StoreModule } from '@ngrx/store';
import { EffectsModule } from '@ngrx/effects';
import { LoginPageComponent } from './containers/login-page/login-page.component';
import { RegisterPageComponent } from './containers/register-page/register-page.component';
import { ForgotPasswordPageComponent } from './containers/forgot-password-page/forgot-password-page.component';
import { LoginFormComponent } from './components/login-form/login-form.component';
import { RegisterFormComponent } from './components/register-form/register-form.component';
import { ForgotPasswordFormComponent } from './components/forgot-password-form/forgot-password-form.component';
import { TermsComponent } from './components/terms/terms.component';

import { AuthService } from './services/auth.service';
import { AuthGuard, LazyAuthGuard } from './services/auth-guard.service';
import { AuthEffects } from './effects/auth.effects';
import { reducers } from './reducers';

import { AuthRoutingModule } from './auth-routing.module';

export const COMPONENTS = [
  LoginPageComponent,
  RegisterPageComponent,
  ForgotPasswordPageComponent,
  LoginFormComponent,
  RegisterFormComponent,
  ForgotPasswordFormComponent,
  TermsComponent,
];

@NgModule({
  imports: [
    CommonModule,
    NativeScriptModule,
    NativeScriptRouterModule,
    NativeScriptFormsModule,
  ],
  declarations: COMPONENTS,
  exports: COMPONENTS,
  entryComponents: [TermsComponent],
  providers: [

     ModalDialogService
   ]
})
export class AuthModule {
  static forRoot(): ModuleWithProviders {
    return {
      ngModule: RootAuthModule,
      providers: [AuthService, AuthGuard, LazyAuthGuard],
    };
  }
}

@NgModule({
  imports: [
    AuthModule,
    AuthRoutingModule,
    StoreModule.forFeature('auth', reducers),
    EffectsModule.forFeature([AuthEffects]),
  ],
})
export class RootAuthModule {}






import { Component } from '@angular/core';
import { ModalDialogParams } from 'nativescript-angular/modal-dialog';
import { Page } from 'ui/page';
@Component({
  moduleId: module.id,
  selector: 'terms-dialog',
  templateUrl: 'terms.component.html',
})
export class TermsComponent  {
  public test = 'sdfsdfsdfsd';
  constructor(private params: ModalDialogParams, private page: Page) {
    this.page.on('unloaded', () => {
      // using the unloaded event to close the modal when there is user interaction
      // e.g. user taps outside the modal page
      this.params.closeCallback();
    });  
  }
  public getText(){
    return 'some text';
  }
  public close() {
    debugger;
    this.params.closeCallback();
  } 
}

<ScrollView sdkExampleTitle sdkToggleNavButton>
  <StackLayout>
    <Label horizontalAlignment="left" text="test" class="m-15 h2" textWrap="true"></Label>
    <Label horizontalAlignment="left" [text]="test" class="m-15 h2" textWrap="true"></Label>
    <Label [text]="getText()" horizontalAlignment="left" class="m-15 h2" textWrap="true"></Label>
     <Button row="0" col="1" horizontalAlignment="right" text="&#xf2d4;" class="fa" id="menu-btn" (tap)="close()"></Button>
  </StackLayout>
</ScrollView>







import {
  Component,
  OnInit,
  Input,
  Output,
  EventEmitter,
  ViewContainerRef,
} from '@angular/core';
import { FormGroup, FormControl } from '@angular/forms';
import { Register } from '../../models/user';
import { Config } from '../../../common/index';
import { TermsComponent } from '../terms/terms.component';

/* Mobile Specific Stuff */
import imagepicker = require('nativescript-imagepicker');
import {
  ModalDialogService,
  ModalDialogOptions,
} from 'nativescript-angular/modal-dialog';

@Component({
  moduleId: module.id,
  selector: 'bc-register-form',
  templateUrl: 'register-form.component.html',
  styleUrls: ['register-form.component.scss'],
})
export class RegisterFormComponent implements OnInit {
  @Input()
  set pending(isPending: boolean) {
    if (isPending) {
      this.form.disable();
    } else {
      this.form.enable();
    }
  }

  @Input() errorMessage: string | null;

  @Output() submitted = new EventEmitter<Register>();

  form: FormGroup = new FormGroup({
    username: new FormControl(''),
    password: new FormControl(''),
    email: new FormControl(''),
    avatar: new FormControl(''),
  });

  avatarFilePreview: any;

  constructor(
    public modalService: ModalDialogService,
    private viewContainer: ViewContainerRef
  ) {}

  ngOnInit() {
    this.form.get('avatar').valueChanges.subscribe(v => {
      this.avatarFilePreview = v._files[0];
    });
  }

  openDialog() {
    const options: ModalDialogOptions = {
      fullscreen: false,
      viewContainerRef: this.viewContainer,
      context: {}
    };

    return this.modalService.showModal(TermsComponent, options);
  }

  getNativescriptImagePicker() {
    let context = imagepicker.create({
      mode: 'single',
    });
    var self = this;
    context
      .authorize()
      .then(function() {
        return context.present();
      })
      .then(function(selection) {
        self.avatarFilePreview = selection[0];
      })
      .catch(function(e) {
        // process error
      });
  }

  submit() {
    if (this.form.valid) {
      this.submitted.emit(this.form.value);
    }
  }
}


{
  "name": "snapnurse-apps",
  "version": "0.0.0",
  "repository": "<fill-your-repository-here>",
  "nativescript": {
    "id": "com.domain.project",
    "tns-android": {
      "version": "3.2.0"
    },
    "tns-ios": {
      "version": "3.2.0"
    }
  },
  "dependencies": {
    "@angular/animations": "~4.1.0",
    "@angular/common": "~4.1.0",
    "@angular/compiler": "~4.1.0",
    "@angular/core": "~4.1.0",
    "@angular/forms": "~4.1.0",
    "@angular/http": "~4.1.0",
    "@angular/platform-browser": "~4.1.0",
    "@angular/platform-browser-dynamic": "~4.1.0",
    "@angular/router": "~4.1.0",
    "@ngrx/db": "^2.0.2",
    "@ngrx/effects": "^4.1.0",
    "@ngrx/entity": "^4.1.0",
    "@ngrx/router-store": "^4.1.0",
    "@ngrx/store": "^4.1.0",
    "@ngrx/store-devtools": "^4.0.0",
    "@ngx-translate/core": "^6.0.1",
    "@ngx-translate/http-loader": "0.0.3",
    "nativescript-angular": "^3.1.0",
    "nativescript-cardview": "^2.0.3",
    "nativescript-imagepicker": "^4.0.1",
    "nativescript-ngx-fonticon": "^3.0.0",
    "nativescript-theme-core": "~1.0.2",
    "ngrx-store-freeze": "^0.2.0",
    "reflect-metadata": "~0.1.8",
    "rxjs": "^5.4.0",
    "tns-core-modules": "^3.3.0",
    "zone.js": "~0.8.2"
  },
  "devDependencies": {
    "@angular/compiler-cli": "~4.1.0",
    "@ngtools/webpack": "~1.5.5",
    "@types/jasmine": "^2.5.47",
    "babel-traverse": "6.25.0",
    "babel-types": "6.25.0",
    "babylon": "6.17.4",
    "copy-webpack-plugin": "~4.0.1",
    "css-loader": "0.28.2",
    "del": "^2.2.2",
    "extract-text-webpack-plugin": "~2.1.0",
    "gulp": "gulpjs/gulp#4.0",
    "gulp-debug": "^3.1.0",
    "gulp-rename": "^1.2.2",
    "gulp-string-replace": "^0.4.0",
    "lazy": "1.0.11",
    "nativescript-css-loader": "~0.26.0",
    "nativescript-dev-android-snapshot": "^0.*.*",
    "nativescript-dev-sass": "^1.3.2",
    "nativescript-dev-typescript": "~0.4.5",
    "nativescript-dev-webpack": "^0.7.3",
    "raw-loader": "~0.5.1",
    "resolve-url-loader": "~2.0.2",
    "typescript": "~2.3.4",
    "webpack": "^3.7.1",
    "webpack-bundle-analyzer": "^2.8.2",
    "webpack-sources": "~0.2.3"
  },
  "scripts": {
    "prepPhone": "gulp build.Phone",
    "prepTablet": "gulp build.Default",
    "prepCLIPhone": "gulp build.cli.Phone",
    "prepCLITablet": "gulp build.cli.Default",
    "ios": "npm run prepCLITablet && tns run ios",
    "ios.phone": "npm run prepCLIPhone && tns run ios",
    "android": "npm run prepCLITablet && tns run android",
    "android.phone": "npm run prepCLIPhone && tns run android",
    "phone-ios-bundle": "npm run prepPhone && tns prepare ios && npm run start-ios-bundle --uglify",
    "tablet-ios-bundle": "npm run prepTablet && tns prepare ios && npm run start-ios-bundle --uglify",
    "build.phone-ios-bundle": "npm run prepPhone && tns prepare ios && npm run build-ios-bundle --uglify",
    "build.tablet-ios-bundle": "npm run prepTablet && tns prepare ios && npm run build-ios-bundle --uglify",
    "phone-android-bundle": "npm run prepPhone && tns prepare android && npm run start-android-bundle --uglify",
    "tablet-android-bundle": "npm run prepTablet && tns prepare android && npm run start-android-bundle --uglify",
    "build.phone-android-bundle": "npm run prepPhone && tns prepare android && npm run build-android-bundle --uglify",
    "build.tablet-android-bundle": "npm run prepTablet && tns prepare android && npm run build-android-bundle --uglify",
    "ns-bundle": "ns-bundle",
    "livesync": "gulp tns.Livesync",
    "livesync.phone": "gulp tns.Livesync.Phone",
    "publish-ios-bundle": "npm run ns-bundle --ios --publish-app",
    "generate-android-snapshot": "generate-android-snapshot --targetArchs arm,arm64,ia32 --install",
    "start-android-bundle": "npm run ns-bundle --android --run-app",
    "start-ios-bundle": "npm run ns-bundle --ios --run-app",
    "build-android-bundle": "npm run ns-bundle --android --build-app",
    "build-ios-bundle": "npm run ns-bundle --ios --build-app"
  }
}
1
Hi Josh. Do you have some code you can share? - Alex Ziskind
@AlexZiskind Sure, although it probably wont help as its all just like the docs. Need really to think about what could cause it as opposed to looking through all code for what is causing it. - Josh
@AlexZiskind I have added the auth modules and the related components - Josh
@AlexZiskind The first label shows correctly since the text is hard coded. The second and third labels don't display at all. And the button hits the debugger in the close() function. How can that be? - Josh
Haven't debugged this yet, but did you include the NativeScriptModule in your AuthModule? - Alex Ziskind

1 Answers

1
votes

After spending hours tracking down this issue, I have come up with a way to fix it.

Create a class called CustomDialogService:

import {
  DetachedLoader,
  ModalDialogOptions,
  ModalDialogParams,
  ModalDialogService,
  PAGE_FACTORY,
  PageFactory
} from "nativescript-angular";
import {
  ComponentFactoryResolver,
  ComponentRef,
  Injectable,
  ReflectiveInjector,
  Type,
  ViewContainerRef
} from "@angular/core";
import {View} from "tns-core-modules/ui/core/view";
import {Page} from "tns-core-modules/ui/page";

interface ShowDialogOptions {
  containerRef: ViewContainerRef;
  context: any;
  doneCallback: any;
  fullscreen: boolean;
  pageFactory: PageFactory;
  parentPage: Page;
  resolver: ComponentFactoryResolver;
  type: Type<any>;
}

@Injectable()
export class CustomDialogService implements ModalDialogService {
  private static showDialog({
                              containerRef,
                              context,
                              doneCallback,
                              fullscreen,
                              pageFactory,
                              parentPage,
                              resolver,
                              type,
                            }: ShowDialogOptions): void {
    const page = pageFactory({isModal: true, componentType: type});

    let detachedLoaderRef: ComponentRef<DetachedLoader>;
    const closeCallback = (...args: any[]) => {
      doneCallback.apply(undefined, args);
      page.closeModal();
      detachedLoaderRef.instance.detectChanges();
      detachedLoaderRef.destroy();
    };

    const modalParams = new ModalDialogParams(context, closeCallback);

    const providers = ReflectiveInjector.resolve([
      {provide: Page, useValue: page},
      {provide: ModalDialogParams, useValue: modalParams},
      {provide: ViewContainerRef, useValue: containerRef}
    ]);

    const childInjector = ReflectiveInjector.fromResolvedProviders(
      providers, containerRef.parentInjector);
    const detachedFactory = resolver.resolveComponentFactory(DetachedLoader);
    detachedLoaderRef = containerRef.createComponent(detachedFactory, -1, childInjector);
    detachedLoaderRef.instance.loadComponent(type).then((compRef) => {
      compRef.changeDetectorRef.detectChanges();
      const componentView = <View>compRef.location.nativeElement;

      if (componentView.parent) {
        (<any>componentView.parent).removeChild(componentView);
      }

      page.content = componentView;
      parentPage.showModal(page, context, closeCallback, fullscreen);
    });
  }

  public showModal(type: Type<any>,
                   {viewContainerRef, moduleRef, context, fullscreen}: ModalDialogOptions): Promise<any> {
    if (!viewContainerRef) {
      throw new Error(
        "No viewContainerRef: " +
        "Make sure you pass viewContainerRef in ModalDialogOptions."
      );
    }

    const parentPage: Page = viewContainerRef.injector.get(Page);
    const pageFactory: PageFactory = viewContainerRef.injector.get(PAGE_FACTORY);

    // resolve from particular module (moduleRef)
    // or from same module as parentPage (viewContainerRef)
    const componentContainer = moduleRef || viewContainerRef;
    const resolver = componentContainer.injector.get(ComponentFactoryResolver);

    return new Promise(resolve => {
      setTimeout(() => CustomDialogService.showDialog({
        containerRef: viewContainerRef,
        context,
        doneCallback: resolve,
        fullscreen: !!fullscreen,
        pageFactory,
        parentPage,
        resolver,
        type,
      }), 10);
    });
  }
}

Then, in your NgModule, add this provider to your providers array:

{ provide: ModalDialogService, useClass: CustomDialogService }

This overrides the default ModalDialogService with our new CustomDialogService which fixes the bug!