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',
},
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 want to use typescript in Vue in Laravel project.
I already checked all tutorials for that but none of them works for me give below
teej.
Titas Gailius.
sebastiandedeyne.
when ever i run 'npm run dev' i get this error
ERROR in ./resources/js/app.ts
Module build failed: Error: You may be using an old version of webpack; please check you're using at least version 4
at successfulTypeScriptInstance (E:\PersonalProjects\web_dev\blog\node_modules\ts-loader\dist\instances.js:144:15)
at Object.getTypeScriptInstance (E:\PersonalProjects\web_dev\blog\node_modules\ts-loader\dist\instances.js:34:12)
at Object.loader (E:\PersonalProjects\web_dev\blog\node_modules\ts-loader\dist\index.js:17:41)
# multi ./resources/js/app.ts ./resources/sass/app.scss
Im facing this problem from quite some days but i was finally be able to find the solution for that so im Sharing my exp to folks who want to use typescript instead of javascript in vue in laravel. so here is the instruction
here the intruction
Laravel 5.7 uses Laravel-mix which down the line uses webpack 3 . Which is not what we want for typescript to work in laravel project.
Initialize your project
Let's create a new Project. You can also do this on Existing project just make sure to convert js code to ts.
First make sure you have composer and laravel installed
sh
laravel new Laravel-Vue-Typecript
Open the project in your any favraite code editor.
Open the package.json and add these packages to devdependencies
{
"devDependencies": {
"auto-loader": "^0.2.0",
"autoprefixer": "^9.4.1",
"axios": "^0.18",
"bootstrap": "^4.0.0",
"lodash": "^4.17.5",
"popper.js": "^1.12",
"jquery": "^3.2",
"cross-env": "^5.1",
"css-loader": "^1.0.1",
"mini-css-extract-plugin": "^0.4.5",
"node-sass": "^4.10.0",
"optimize-css-assets-webpack-plugin": "^5.0.1",
"postcss-loader": "^3.0.0",
"sass-loader": "^7.1.0",
"ts-loader": "^5.3.1",
"typescript": "^3.2.1",
"uglifyjs-webpack-plugin": "^2.0.1",
"vue": "^2.5.17",
"vue-class-component": "^6.3.2",
"vue-property-decorator": "^7.2.0",
"webpack": "^4.26.1",
"webpack-cli": "^3.1.2",
"vue-loader": "^15.4.2",
"vue-template-compiler": "^2.5.17"
}
}
Now install these npm packages with
npm install
Add Typescript Support
Then rename these files
Laravel-Vue-Typecript/
├─ resources/js/app.js => resources/js/app.ts
└─ resources/js/bootstrap.js => resources/js/bootstrap.ts
Now Change the Code in app.ts, bootstrap.ts and
resources/js/components/ExampleComponent.vue
// app.ts
import "./bootstrap"
import Vue from "vue"
import ExampleComponent from "./components/ExampleComponent.vue"
Vue.component('example', ExampleComponent)
new Vue({
el: '#app'
})
// bootstrap.ts
import axios from 'axios';
import * as _ from 'lodash';
import jQuery from 'jquery';
import * as Popper from 'popper.js';
import 'bootstrap';
axios.defaults.headers.common['X-Requested-With'] = 'XMLHttpRequest';
let token : HTMLMetaElement | null = document.head!.querySelector('meta[name="csrf-token"]');
if (token) {
axios.defaults.headers.common['X-CSRF-TOKEN'] = token.content;
} else {
console.error('CSRF token not found: https://laravel.com/docs/csrf#csrf-x-csrf-token');
}
// resources/js/components/ExampleComponent.vue
<template>
<h1>This is an example component</h1>
</template>
<script lang="ts">
import Vue from 'vue'
import Component from "vue-class-component"
#Component
export default class ExampleComponent extends Vue {
mounted() : void {
console.log("hello");
}
}
</script>
```
Create typings.d.ts file inside resources/js and add these lines.
declare module '*.vue' {
import Vue from 'vue'
export default Vue
}
declare module 'jquery';
declare module 'lodash';
Now Create tsconfig.json, webpack.config.js and postcss.config.js in the root of your project and these lines of code to them respectivly
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"strict": true,
"module": "es2015",
"moduleResolution": "node",
"experimentalDecorators": true,
"skipLibCheck": true
},
"include": [
"resources/js/**/*"
],
"exclude": [
"node_modules",
"vendor"
]
}
webpack.config.json
const path = require('path')
const UglifyJsPlugin = require('uglifyjs-webpack-plugin')
const MiniCssExtractPlugin = require('mini-css-extract-plugin')
const OptimizeCSSAssetsPlugin = require('optimize-css-assets-webpack-plugin')
const VueLoaderPlugin = require('vue-loader/lib/plugin')
const autoprefixer = require('autoprefixer');
const webpack = require('webpack');
let env = process.env.NODE_ENV
let isDev = env === 'development'
const WEBPACK_CONFIG = {
mode: env,
entry: {
app: ['./resources/js/app.ts', './resources/sass/app.scss'],
},
output: {
publicPath: './public',
path: path.resolve(__dirname, 'public'),
filename: 'js/[name].js',
chunkFilename: 'js/chunks/app.js'
},
module: {
rules: [{
test: /\.tsx?$/,
loader: 'ts-loader',
options: { appendTsSuffixTo: [/\.vue$/] },
exclude: /node_modules/,
},
{
test: /\.vue$/,
loader: 'vue-loader'
},
{
test: /\.scss$/,
use: [
MiniCssExtractPlugin.loader,
'css-loader',
'postcss-loader',
'sass-loader'
],
exclude: /node_modules/,
}
],
},
plugins: [
new MiniCssExtractPlugin({
filename: 'css/[name].css'
}),
new VueLoaderPlugin(),
new webpack.LoaderOptionsPlugin({
options: {
postcss: [
autoprefixer()
]
}
})
],
resolve: {
extensions: ['.js', '.jsx', '.vue', '.ts', '.tsx'],
alias: {
vue$: 'vue/dist/vue.esm.js',
},
},
optimization: {
splitChunks: {
chunks: 'async',
minSize: 30000,
maxSize: 0,
minChunks: 1,
maxAsyncRequests: 5,
maxInitialRequests: 3,
automaticNameDelimiter: '~',
name: true,
cacheGroups: {
vendors: {
test: /[\\/]node_modules[\\/]/,
priority: -10
},
default: {
minChunks: 2,
priority: -20,
reuseExistingChunk: true
}
}
}
}
}
if (!isDev) {
WEBPACK_CONFIG.optimization = {
minimizer: [
new UglifyJsPlugin({
cache: true,
parallel: true,
sourceMap: true
}),
new OptimizeCSSAssetsPlugin({})
]
}
WEBPACK_CONFIG.plugins.push(
new webpack.DefinePlugin({
'process.env': {
NODE_ENV: 'production'
}
})
)
}
module.exports = WEBPACK_CONFIG
postcss.config.js
module.exports = {
plugins: {
'autoprefixer': {}
} }
Now finally change the "scripts" in package.json
"scripts": {
"dev": "npm run development",
"development": "cross-env NODE_ENV=development node_modules/webpack/bin/webpack.js --progress --hide-modules --config=webpack.config.js",
"watch": "npm run development -- --watch",
"watch-poll": "npm run watch -- --watch-poll",
"prod": "npm run production",
"production": "cross-env NODE_ENV=production node_modules/webpack/bin/webpack.js --no-progress --hide-modules --config=webpack.config.js"
},
Finally build the project
and run the npm scripts by
npm run dev // To build the Project
npm run watch // To build and watch for files changes and build automagically
npm run prod // for production
I am currently want to test my effects with ngrx/effects. I followed the markdown but I have an error when I want run my test.
Cannot find module 'rxjs/testing' from 'jasmine-marbles.umd.js'
Here is my code (For the moment I did not do expectations, I just want my test runs) :
import { TestBed } from '#angular/core/testing'
import { provideMockActions } from '#ngrx/effects/testing'
import { ReplaySubject } from 'rxjs/ReplaySubject'
import { hot, cold } from 'jasmine-marbles'
import { Observable } from 'rxjs/Observable'
import { VersionService } from '../../service/version/version.service'
import { DataEffects } from './data.effect'
import { RequestVersions, ReceiveVersions } from './data.action'
describe('My Effects', () => {
let versionService: VersionService
let effects: DataEffects
let actions: Observable<any>
beforeEach(() => {
TestBed.configureTestingModule({
imports: [
// any modules needed
],
providers: [
DataEffects,
provideMockActions(() => actions),
{provide: VersionService, useValue: {
getVersions: jest.fn().mockReturnValueOnce({data: {versions: 'MOCKED_VERSION_RESULT'}})
}}
],
})
versionService = TestBed.get(VersionService)
effects = TestBed.get(DataEffects)
})
it('should work', () => {
const action = new RequestVersions()
const completion = new ReceiveVersions('MOCKED_VERSION_RESULT')
actions = hot('--a-', { a: action });
const expected = cold('--b', { b: completion })
})
}
And here what my package.json look like :
[...]
"dependencies": {
[... all #angular import ...]
"#ngrx/effects": "^5.2.0",
"#ngrx/store": "^5.2.0",
"#ngrx/store-devtools": "^5.2.0",
"bootstrap": "^4.0.0",
"core-js": "^2.4.1",
"rxjs": "^5.5.6",
"zone.js": "^0.8.19"
},
"devDependencies": {
"#angular/cli": "1.6.7",
"#angular/compiler-cli": "^5.2.0",
"#angular/language-service": "^5.2.0",
"#types/jest": "^22.1.4",
"#types/node": "~6.0.60",
"codelyzer": "^4.0.1",
"jasmine-marbles": "^0.3.0",
"jest": "^22.4.2",
"jest-junit": "^3.6.0",
"jest-preset-angular": "^5.2.0",
"ts-node": "~4.1.0",
"tslint": "~5.9.1",
"typescript": "~2.5.3"
}
[...]
Can anyone help me in this matter ?
Just ran into this as well.
In my case using jasmine-marbles#v0.2.0 instead of the #latest resolve it, since it doesn't seem to require the peer dependency of the beta rxjs 6 release
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!