Related
It's 3 days i am on this problem of E2E tests on my GraphQL application with NestJS + Apollo / Express.
When I am running my app with serverless offline or directly with my main file, it's working perfectly. I have my graph and all things I need :)
But, when I am running E2E tests with Jest, I received an error from the await app.init() inside the beforeEach.
After playing with the package.json and dependencies, the error throwed is TypeError: (0 , schema_1.makeExecutableSchema) is not a function.
Someone have any idea please ? I am totally blocked ... :(
FAIL src/__tests/graphql/common.e2e-spec.ts
GraphQL - CommonModule (e2e)
Query - Test
✕ should return the test query with typename TestSuccess (code: 200) (385 ms)
● GraphQL - CommonModule (e2e) › Query - Test › should return the test query with typename TestSuccess (code: 200)
TypeError: (0 , schema_1.makeExecutableSchema) is not a function
15 | app = moduleFixture.createNestApplication();
16 | app.useGlobalPipes(new ValidationPipe());
> 17 | await app.init();
| ^
18 |
19 | request = superRequest(app.getHttpServer());
20 | });
at GraphQLFactory.mergeWithSchema (node_modules/#nestjs/graphql/dist/graphql.factory.js:30:72)
at ApolloDriver.start (node_modules/#nestjs/apollo/dist/drivers/apollo.driver.js:19:25)
at GraphQLModule.onModuleInit (node_modules/#nestjs/graphql/dist/graphql.module.js:103:9)
at Object.callModuleInitHook (node_modules/#nestjs/core/hooks/on-module-init.hook.js:51:9)
at Proxy.callInitHook (node_modules/#nestjs/core/nest-application-context.js:179:13)
at Proxy.init (node_modules/#nestjs/core/nest-application.js:96:9)
at Object.<anonymous> (src/__tests/graphql/common.e2e-spec.ts:17:5)
package.json
"dependencies": {
"#nestjs/apollo": "^10.0.4",
"#nestjs/common": "^8.3.1",
"#nestjs/core": "^8.3.1",
"#nestjs/graphql": "^10.0.4",
"#nestjs/jwt": "^8.0.0",
"#nestjs/passport": "^8.2.1",
"#nestjs/platform-express": "^8.3.1",
"#prisma/client": "^3.9.2",
"#vendia/serverless-express": "^4.5.3",
"apollo-server-core": "^3.6.3",
"apollo-server-express": "^3.6.3",
"apollo-server-plugin-base": "^3.5.1",
"aws-lambda": "^1.0.7",
"bcryptjs": "^2.4.3",
"class-transformer": "^0.5.1",
"class-validator": "^0.13.2",
"dotenv": "^16.0.0",
"env-var": "^7.1.1",
"express": "^4.17.1",
"graphql": "^16.3.0",
"graphql-query-complexity": "^0.11.0",
"nestjs-pino": "^2.5.0",
"passport": "^0.5.2",
"passport-jwt": "^4.0.0",
"pg": "^8.7.3",
"pino-http": "^6.6.0",
"reflect-metadata": "^0.1.13",
"rimraf": "^3.0.2",
"rxjs": "^7.5.3"
},
"devDependencies": {
"#faker-js/faker": "^6.0.0-alpha.5",
"#nestjs/cli": "^8.2.0",
"#nestjs/schematics": "^8.0.0",
"#nestjs/testing": "^8.0.0",
"#serverless/typescript": "^3.2.0",
"#types/aws-lambda": "^8.10.92",
"#types/bcryptjs": "^2.4.2",
"#types/express": "^4.17.13",
"#types/jest": "27.4.0",
"#types/node": "^16.0.0",
"#types/passport-local": "^1.0.34",
"#types/supertest": "^2.0.11",
"#typescript-eslint/eslint-plugin": "^5.0.0",
"#typescript-eslint/parser": "^5.0.0",
"apollo-server-testing": "^2.25.3",
"eslint": "^8.0.1",
"eslint-config-prettier": "^8.3.0",
"eslint-plugin-prettier": "^4.0.0",
"jest": "^27.5.1",
"npm-run-all": "^4.1.5",
"prettier": "^2.5.1",
"prisma": "^3.9.2",
"prisma-nestjs-graphql": "^14.6.1",
"serverless": "^3.2.1",
"serverless-offline": "^8.4.0",
"serverless-plugin-optimize": "^4.2.1-rc.1",
"serverless-plugin-warmup": "^7.0.2",
"source-map-support": "^0.5.21",
"supertest": "^6.2.2",
"ts-jest": "^27.1.3",
"ts-loader": "^9.2.3",
"ts-morph": "^13.0.3",
"ts-node": "^10.5.0",
"tsc-alias": "^1.5.0",
"tsconfig-paths": "^3.12.0",
"typescript": "^4.5.5",
"webpack": "^5.0.0"
}
jest.config.ts
module.exports = {
moduleFileExtensions: ['js', 'json', 'ts'],
testEnvironment: 'node',
transform: {
'^.+\\.(t|j)s$': 'ts-jest',
},
testRegex: '.(spec|e2e-spec).ts$',
moduleNameMapper: {
'^#features/(.*)': '<rootDir>/src/features/$1',
'^#tests/(.*)': '<rootDir>/src/__tests/$1',
'^#graphql': '<rootDir>/src/#graphql/generated',
'^#types': '<rootDir>/src/#types',
'^#utils': '<rootDir>/src/utils',
'^#config': '<rootDir>/src/config',
},
testPathIgnorePatterns: [
'<rootDir>/dist/',
'<rootDir>/prisma/',
'<rootDir>/bin/',
'<rootDir>/node_modules/',
'<rootDir>/.github/',
],
};
graphql.e2e-spec.ts
import { GraphQLModule } from '#features/graphql/graphql.module';
import { INestApplication, ValidationPipe } from '#nestjs/common';
import { Test, TestingModule } from '#nestjs/testing';
import superRequest, { SuperTest, Test as TestItem } from 'supertest';
describe('GraphQL - CommonModule (e2e)', () => {
let app: INestApplication;
let request: SuperTest<TestItem>;
beforeEach(async () => {
const moduleFixture: TestingModule = await Test.createTestingModule({
imports: [GraphQLModule],
}).compile();
app = moduleFixture.createNestApplication();
app.useGlobalPipes(new ValidationPipe());
await app.init();
request = superRequest(app.getHttpServer());
});
describe('Query - Test', () => {
it('should return the test query with typename TestSuccess (code: 200)', async () => {
return request
.post('/graphql')
.send({
query: `
query {
test {
__typename
}
}`,
})
.then((res) => {
expect(res.status).toBe(200);
expect(res.body.data.test.__typename).toBe('TestSuccess');
});
});
});
});
graphql.module.ts
import { config } from '#config';
import { AppService } from '#features/app.service';
import { GraphQLAuthModule } from '#features/graphql/auth/auth.module';
import { CommonModule } from '#features/graphql/common/common.module';
import { UserModule } from '#features/graphql/user/user.module';
import { GraphQLComplexityPlugin } from '#features/graphql/_plugins';
import { ApolloDriver, ApolloDriverConfig } from '#nestjs/apollo';
import { Module } from '#nestjs/common';
import { GraphQLModule as NESTJSGraphQLModule } from '#nestjs/graphql';
import { join } from 'path';
#Module({
imports: [
GraphQLAuthModule,
NESTJSGraphQLModule.forRoot<ApolloDriverConfig>({
driver: ApolloDriver,
autoSchemaFile: join(process.cwd(), 'src/#graphql/schema.gql'),
sortSchema: true,
debug: config.graphql.isDebugEnabled,
introspection: true,
playground: config.graphql.isPlaygroundEnabled
? {
settings: { 'schema.polling.enable': false },
}
: false,
}),
UserModule,
CommonModule,
],
providers: [AppService, GraphQLComplexityPlugin],
})
export class GraphQLModule {
constructor(private readonly appService: AppService) {
if (!this.appService.checkEnv()) process.exit();
}
}
After many hours of intense programming ...
I finally found the problem.
Be careful if you use some "alias" for imports, because it can overwrite some of the packages used.
Here, i use the #graphql alias, and it breaks all my tests.
When I remove it, the problem disappear.
moduleNameMapper: {
'^#features/(.*)': '<rootDir>/src/features/$1',
'^#tests/(.*)': '<rootDir>/__tests/$1',
'^#utils': '<rootDir>/src/utils',
'^#config': '<rootDir>/src/config',
'^#types': '<rootDir>/src/#types',
'^#graphql': '<rootDir>/src/#graphql/generated', // to remove
},
to
moduleNameMapper: {
'^#features/(.*)': '<rootDir>/src/features/$1',
'^#tests/(.*)': '<rootDir>/__tests/$1',
'^#utils': '<rootDir>/src/utils',
'^#config': '<rootDir>/src/config',
'^#types': '<rootDir>/src/#types',
},
I created a directive to disable the context menu on android and ios app in Nativescript.
import { Directive, OnInit, OnDestroy, ElementRef, Renderer2 } from "#angular/core";
import { isIOS } from "tns-core-modules/platform";
import * as utils from "tns-core-modules/utils/utils";
import { EventData } from "tns-core-modules/data/observable";
import { TextField } from "tns-core-modules/ui/text-field";
declare var UITextField, CGRectMake, android;
if (isIOS) {
UITextField.prototype._originalCanPerformActionWithSender = UITextField.prototype.canPerformActionWithSender;
UITextField.prototype.canPerformActionWithSender = function (action, sender) {
if (this.disableMenu) {
return false;
}
return UITextField.prototype._originalCanPerformActionWithSender.call(this, action, sender)
};
}
#Directive({
selector: "[disableCutCopyPaste]"
})
export class DisableCutCopyPasteDirective implements OnInit, OnDestroy {
listener: () => void;
constructor(private renderer: Renderer2, private el: ElementRef) {
}
ngOnInit() {
this.listener = this.renderer.listen(this.el.nativeElement, TextField.loadedEvent, (event: EventData) => {
const textField = <TextField>event.object;
if (isIOS) {
Object.defineProperty(textField.nativeView, "disableMenu", {
get: function () {
return true;
}
});
} else {
textField.nativeView.setLongClickable(false);
textField.nativeView.setOnTouchListener(new android.view.View.OnTouchListener({
onTouch: function (view, event) {
if (event.getAction() == android.view.MotionEvent.ACTION_UP) {
view.requestFocus();
utils.ad.showSoftInput(view);
}
return true;
}
}));
}
});
}
ngOnDestroy() {
this.removeListener();
}
private removeListener() {
if (this.listener) {
this.listener();
this.listener = null;
}
}
}
This code is working fine on Android devices but the iOS app is crashing and I'm getting the following error
TypeError: Attempting to change the getter of an unconfigurable property
at this line
Object.defineProperty(textField.nativeView, "disableMenu", {
Can anyone tell me what's causing this issue?
My package.json
{
"nativescript": {
"id": "com.abcde.app",
"tns-android": {
"version": "6.1.1"
},
"tns-ios": {
"version": "6.5.3"
}
},
"description": "NativeScript Application",
"license": "SEE LICENSE IN <your-license-filename>",
"repository": "<fill-your-repository-here>",
"dependencies": {
"#angular/animations": "~8.2.9",
"#angular/common": "~8.2.9",
"#angular/compiler": "~8.2.9",
"#angular/core": "~8.2.9",
"#angular/forms": "~8.2.9",
"#angular/http": "8.0.0-beta.10",
"#angular/platform-browser": "~8.2.9",
"#angular/platform-browser-dynamic": "~8.2.9",
"#angular/router": "~8.2.9",
"#nstudio/nativescript-checkbox": "^1.0.0",
"#nstudio/nativescript-loading-indicator": "^1.0.0",
"nativescript-angular": "^8.2.1",
"nativescript-carousel": "^6.1.1",
"nativescript-checkbox": "^3.0.3",
"nativescript-drop-down": "^5.0.4",
"nativescript-exit": "^1.0.1",
"nativescript-floatingactionbutton": "^5.1.0",
"nativescript-iqkeyboardmanager": "^1.5.1",
"nativescript-modal-datetimepicker": "^1.2.3",
"nativescript-plugin-firebase": "^10.0.1",
"nativescript-root-detection": "^1.0.0",
"nativescript-theme-core": "~1.0.4",
"nativescript-ui-listview": "7.1.0",
"reflect-metadata": "~0.1.13",
"rxjs": "^6.5.3",
"rxjs-compat": "^6.5.3",
"simple-crypto-js": "^2.2.0",
"tns-core-modules": "^6.5.21",
"tns-platform-declarations": "^6.5.15",
"zone.js": "0.9.1"
},
"devDependencies": {
"#angular/compiler-cli": "8.2.9",
"#nativescript/schematics": "~0.7.2",
"#ngtools/webpack": "8.3.8",
"nativescript-dev-webpack": "^1.3.0",
"tns-android": "6.1.1",
"typescript": "~3.5.3"
},
"gitHead": "9b65dfgdgdfgdgdgdfgdfd818a8205e",
"readme": "NativeScript Application"
}
Playground Link
Anyone have this error appear when trying to install the WYSIWYG editor Tiptap for Vuefity?
This is the error:
ERROR in ./node_modules/vuetify/src/styles/main.sass (./node_modules/css-loader/dist/cjs.js??ref--4-1!./node_modules/postcss-loader/src??ref--4-2!./node_modules/sass-loader/dist/cjs.js??ref--4-3!./node_modules/vuetify/src/styles/main.sass)
Module build failed (from ./node_modules/sass-loader/dist/cjs.js):
SassError: Invalid CSS after " #content": expected "}", was "($material-light); "
on line 3 of node_modules/vuetify/src/styles/tools/_theme.sass
from line 6 of node_modules/vuetify/src/styles/tools/_index.sass
from line 3 of /home/fc-gui/node_modules/vuetify/src/styles/main.sass
>> #content($material-light); }
----^
And then this is my main Vue file:
/* eslint no-console: 0 */
import '#mdi/font/css/materialdesignicons.css'
import Vue from 'vue'
import VueRouter from 'vue-router'
import App from '../app.vue'
import Vuetify from 'vuetify'
import getShop from '../api/shops'
import customizationApi from '../api/customization'
import { routes } from '../router/routes'
import store from '../store/store'
import axios from 'axios'
import VueFriendlyIframe from 'vue-friendly-iframe';
import { TiptapVuetifyPlugin } from 'tiptap-vuetify'
Vue.use(VueFriendlyIframe)
Vue.use(Vuetify)
Vue.use(VueRouter)
Vue.use(TiptapVuetifyPlugin)
const router = new VueRouter({
routes
});
router.beforeEach((to, from, next) => {
if(to.name == null){
getShop.get(
data => {
if(data.setup) {
next({ name: 'emailCustomize'});
}
else {
next({ name: 'plans'})
}
});
}
else {
next();
}
});
document.addEventListener('DOMContentLoaded', () => {
const app = new Vue({
vuetify: new Vuetify(),
router,
store,
render: h => h(App)
}).$mount()
document.body.appendChild(app.$el)
})
And finally my package.json file
{
"name": "fresh-credit",
"private": true,
"dependencies": {
"#rails/actioncable": "^6.0.0-alpha",
"#rails/activestorage": "^6.0.0-alpha",
"#rails/ujs": "^6.0.0-alpha",
"#rails/webpacker": "^4.0.7",
"animate.css": "^3.7.2",
"axios": "^0.19.0",
"babel-preset-stage-2": "^6.24.1",
"chart.js": "^2.8.0",
"lodash": "^4.17.15",
"postcss-loader": "^3.0.0",
"stylus": "^0.54.7",
"stylus-loader": "^3.0.2",
"tiptap-extensions": "^1.28.6",
"tiptap-vuetify": "^2.13.2",
"turbolinks": "^5.2.0",
"vue": "^2.6.10",
"vue-breadcrumbs": "^1.2.0",
"vue-chartist": "^2.2.1",
"vue-chartjs": "^3.4.2",
"vue-click-outside": "^1.0.7",
"vue-friendly-iframe": "^0.17.0",
"vue-loader": "^15.7.1",
"vue-router": "^3.1.3",
"vue-template-compiler": "^2.6.10",
"vuetify": "^2.0.18",
"vuex": "^3.1.2"
},
"version": "0.1.0",
"devDependencies": {
"#mdi/font": "^4.4.95",
"deepmerge": "^4.2.2",
"fibers": "^4.0.2",
"sass": "^1.25.0",
"sass-loader": "^8.0.2",
"webpack-dev-server": "^3.8.0",
"webpack-merge": "^4.1.0"
}
}
Nothing seems to be working. I even took a look at those files mentioned in the error and it doesn't look like that's the source of the issue. But for completeness here is the file _theme.sass
#mixin theme ($component)
.theme--light.#{$component}
#content($material-light)
.theme--dark.#{$component}
#content($material-dark)
Throw this in your Webpack.config.js rules and specify the options based on the sass-loader version you are using
{
test: /\.s(c|a)ss$/,
use: [
'vue-style-loader',
'css-loader',
{
loader: 'sass-loader',
// Requires sass-loader#^7.0.0
options: {
implementation: require('sass'),
fiber: require('fibers'),
indentedSyntax: true // optional
},
// Requires sass-loader#^8.0.0
options: {
implementation: require('sass'),
sassOptions: {
fiber: require('fibers'),
indentedSyntax: true // optional
},
},
},
],
},
And ensure that you are using sass instead of node-sass in your package.json. from my experience I found that if you have had node-sass previously installed, you might want to delete node modules and re-run npm install
You might also want to install vue-style-loader and css-loader as i don't see them in your package.json
More information can be found in these links
https://github.com/vuetifyjs/vuetify/issues/7950
https://vuetifyjs.com/en/introduction/frequently-asked-questions/
https://github.com/vuetifyjs/vuetify/issues/7323
I'm new with NativeScript and I want to use a state management NGXS and implement to my app. I have installed NGXS with NPM: #ngxs/store #ngxs/logger-plugin and #ngxs/devtools-plugin.
So I added those NGXS module to my app.module.
import { NgModule, NO_ERRORS_SCHEMA } from "#angular/core";
import { NativeScriptModule } from "nativescript-angular/nativescript.module";
import { NativeScriptUISideDrawerModule } from "nativescript-ui-sidedrawer/angular";
import { NgxsModule } from '#ngxs/store';
import { NgxsLoggerPluginModule } from '#ngxs/logger-plugin';
import { NgxsReduxDevtoolsPluginModule } from '#ngxs/devtools-plugin';
import { AppRoutingModule } from "./app-routing.module";
import { AppComponent } from "./app.component";
import { PetState } from './_ngxs/pet/pet.state';
#NgModule({
bootstrap: [
AppComponent
],
imports: [
AppRoutingModule,
NativeScriptModule,
NativeScriptUISideDrawerModule,
NgxsModule.forRoot([
//if i uncomment PetState, in console log show me error, Failed to find module: "./pet.actions"
// PetState
]),
//if i uncomment `NgxsLoggerPluginModule.forRoot()`, in console log show me error, Failed to find module: "#ngxs/logger-plugin
// NgxsLoggerPluginModule.forRoot(),
//if i uncomment `NgxsReduxDevtoolsPluginModule.forRoot()`, in console log show me error, Failed to find module: "#ngxs/devstool-plugin
// NgxsReduxDevtoolsPluginModule.forRoot()
],
declarations: [
AppComponent
],
schemas: [
NO_ERRORS_SCHEMA
]
})
export class AppModule { }
After I added the ngxs module, I got 2 issues.
under NgxsModule.forRoot if I uncomment PetState it throw me error in console log, Failed to find module: "./pet.actions". Please refer
If I uncomment NgxsLoggerPluginModule.forRoot() / NgxsReduxDevtoolsPluginModule.forRoot() I will get error in console log and saying failed to find module #ngxs/logger-plugin / #ngxs/devstool-plugin
below is pet.state.ts codes
import { State, Action, Selector, StateContext } from '#ngxs/store';
import { Pet } from './pet.model';
import { AddPet, RemovePet } from './pet.actions';
export class PetStateModel {
pets: Pet[];
}
#State<PetStateModel>({
name: 'Pet',
defaults: {
pets: []
}
})
export class PetState {
#Selector()
static getPet(state: PetStateModel) {
return state.pets;
}
#Action(AddPet)
addPet({getState, patchState}: StateContext<PetStateModel>, { payload }: AddPet) {
const state = getState();
patchState({
pets: [...state.pets, payload]
})
}
#Action(RemovePet)
removePet({getState, patchState}: StateContext<PetStateModel>, { payload }: RemovePet) {
const state = getState();
patchState({
pets: state.pets.filter(a => a.name !== payload )
})
}
}
I really need someone can give some hand to help me fix the issues,
thanks,
Updated
here is my package.json
{
"nativescript": {
"id": "org.nativescript.pledgeCareSample",
"tns-android": {
"version": "5.2.1"
},
"tns-ios": {
"version": "5.2.0"
}
},
"description": "NativeScript Application",
"license": "SEE LICENSE IN <your-license-filename>",
"repository": "<fill-your-repository-here>",
"scripts": {
"lint": "tslint \"src/**/*.ts\""
},
"dependencies": {
"#angular/animations": "~7.2.0",
"#angular/common": "~7.2.0",
"#angular/compiler": "~7.2.0",
"#angular/core": "~7.2.0",
"#angular/forms": "~7.2.0",
"#angular/http": "~7.2.0",
"#angular/platform-browser": "~7.2.0",
"#angular/platform-browser-dynamic": "~7.2.0",
"#angular/router": "~7.2.0",
"#ngxs/storage-plugin": "^3.4.3",
"#ngxs/store": "^3.4.3",
"nativescript-angular": "~7.2.1",
"nativescript-theme-core": "~1.0.4",
"nativescript-ui-sidedrawer": "~5.1.0",
"nativescript-unit-test-runner": "^0.6.0",
"reflect-metadata": "~0.1.12",
"rxjs": "~6.3.0",
"tns-core-modules": "~5.2.0",
"zone.js": "~0.8.26"
},
"devDependencies": {
"#angular/compiler-cli": "~7.2.0",
"#nativescript/schematics": "~0.5.0",
"#ngtools/webpack": "~7.2.0",
"#ngxs/devtools-plugin": "^3.4.3",
"#ngxs/logger-plugin": "^3.4.3",
"#types/jasmine": "^3.3.12",
"codelyzer": "~4.5.0",
"karma": "4.0.1",
"karma-jasmine": "2.0.1",
"karma-nativescript-launcher": "0.4.0",
"nativescript-dev-sass": "~1.6.0",
"nativescript-dev-typescript": "~0.8.0",
"nativescript-dev-webpack": "~0.20.0",
"tslint": "~5.11.0"
},
"gitHead": "f548ec926e75201ab1b7c4a3a7ceefe7a4db15af",
"readme": "NativeScript Application"
}
Ok I found the solution. After few hours I found this solution here and with my solutions
Just need to remove node_modules, hook and platform folder then npm i and add the platform for both ios and android.
and 1 more thing I am not sure why, I uninstalled for #ngxs/logger-plugin and #ngxs/devtools-plugin and reinstall back to the dependency not in dev dependency.
anyway, for now it work for me.
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="" 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"
}
}
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!