I have a rails app which is using webpacker (6.0.0-rc.5) which uses webpack (5.73.0) and I have created a simple app consisting of one NgModule, and one component.
# main.ts (entrypoint)
import { platformBrowserDynamic } from '#angular/platform-browser-dynamic';
import { AppModule } from 'app.module';
import 'zone.js';
platformBrowserDynamic().bootstrapModule(AppModule).catch(console.error);
# app.module.ts
import { NgModule } from '#angular/core';
import { CommonModule } from '#angular/common';
import { AppComponent } from './app.component';
import { BrowserModule } from '#angular/platform-browser';
#NgModule({
declarations: [
AppComponent
],
imports: [
BrowserModule,
CommonModule
],
bootstrap: [
AppComponent
],
})
export class AppModule { }
# app.component.ts
import { Component } from '#angular/core';
#Component({
selector: 'app-root',
template: '<h1>hello from rails<h1>',
})
export class AppComponent { }
This works fine... However, if I try to put ANYTHING in the constructor of the component, such as:
# my.service.ts
import { Injectable } from '#angular/core';
#Injectable({ providedIn: 'root' })
export class MyService {}
# app.component.ts
import { Component } from '#angular/core';
import { MyService } from './my.service';
#Component({
selector: 'app-root',
template: '<h1>hello<h1>',
})
export class AppComponent {
constructor (private service: MyService) {
}
}
I get:
core.mjs:7640 ERROR Error: NG0202: This constructor is not compatible
with Angular Dependency Injection because its dependency at index 0 of
the parameter list is invalid. This can happen if the dependency type
is a primitive like a string or if an ancestor of this class is
missing an Angular decorator.
Please check that 1) the type for the parameter at index 0 is correct
and 2) the correct Angular decorators are defined for this class and
its ancestors.
at ɵɵinvalidFactoryDep (core.mjs:4791:1)
at NodeInjectorFactory.AppComponent_Factory [as factory] (ɵfac.js? [sm]:1:1)
at getNodeInjectable (core.mjs:3516:1)
at instantiateRootComponent (core.mjs:12587:1)
at createRootComponent (core.mjs:14092:1)
at ComponentFactory.create (core.mjs:13969:1)
at ApplicationRef.bootstrap (core.mjs:27442:1)
at core.mjs:27102:1
at Array.forEach ()
at PlatformRef._moduleDoBootstrap (core.mjs:27102:1)
As an experiment, I manually bootstrapped the component, and did:
platformBrowserDynamic().bootstrapModule(AppModule).then(platformRef => {
console.log(platformRef.injector.get(MyService));
}
I can see the service, so it IS in the injector..
For a test, used angular cli to create a new project, and copied over the .ts files from the rails project, and ran them via ng serve. Everything works fine.. Dependency injection works and my service is available via the constructor function...
So the only thing I can think of is, SOMEHOW, dependency injection tokens are getting messed up by webpack...
The js generated from webpack in the rails app is:
"use strict";
(self["webpackChunkmy_app"] = self["webpackChunkmy_app"] || []).push([["main"],{
/***/ "./app/javascript/entrypoints/main.ts":
/*!********************************************!*\
!*** ./app/javascript/entrypoints/main.ts ***!
\********************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _angular_platform_browser_dynamic__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! #angular/platform-browser-dynamic */ "./node_modules/#angular/platform-browser-dynamic/fesm2020/platform-browser-dynamic.mjs");
/* harmony import */ var _ng_app_module__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ../ng/app.module */ "./app/javascript/ng/app.module.ts");
/* harmony import */ var zone_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! zone.js */ "./node_modules/zone.js/fesm2015/zone.js");
/* harmony import */ var zone_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(zone_js__WEBPACK_IMPORTED_MODULE_1__);
(0,_angular_platform_browser_dynamic__WEBPACK_IMPORTED_MODULE_2__.platformBrowserDynamic)().bootstrapModule(_ng_app_module__WEBPACK_IMPORTED_MODULE_0__.AppModule).catch(console.error);
/***/ }),
/***/ "./app/javascript/ng/app.component.ts":
/*!********************************************!*\
!*** ./app/javascript/ng/app.component.ts ***!
\********************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "AppComponent": function() { return /* binding */ AppComponent; }
/* harmony export */ });
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! #angular/core */ "./node_modules/#angular/core/fesm2020/core.mjs");
/* harmony import */ var _my_service__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./my.service */ "./app/javascript/ng/my.service.ts");
var __decorate = undefined && undefined.__decorate || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __metadata = undefined && undefined.__metadata || function (k, v) {
if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v);
};
let AppComponent = class AppComponent {
constructor(service) {
this.service = service;
console.log(service);
}
};
AppComponent = __decorate([(0,_angular_core__WEBPACK_IMPORTED_MODULE_1__.Component)({
selector: 'app-root',
template: '<h1>hello from rails<h1>'
}), __metadata("design:paramtypes", [_my_service__WEBPACK_IMPORTED_MODULE_0__.MyService])], AppComponent);
/***/ }),
/***/ "./app/javascript/ng/app.module.ts":
/*!*****************************************!*\
!*** ./app/javascript/ng/app.module.ts ***!
\*****************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "AppModule": function() { return /* binding */ AppModule; }
/* harmony export */ });
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! #angular/core */ "./node_modules/#angular/core/fesm2020/core.mjs");
/* harmony import */ var _angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! #angular/platform-browser */ "./node_modules/#angular/platform-browser/fesm2020/platform-browser.mjs");
/* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! #angular/common */ "./node_modules/#angular/common/fesm2020/common.mjs");
/* harmony import */ var _app_component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./app.component */ "./app/javascript/ng/app.component.ts");
var __decorate = undefined && undefined.__decorate || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
let AppModule = class AppModule {};
AppModule = __decorate([(0,_angular_core__WEBPACK_IMPORTED_MODULE_1__.NgModule)({
declarations: [_app_component__WEBPACK_IMPORTED_MODULE_0__.AppComponent],
imports: [_angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__.BrowserModule, _angular_common__WEBPACK_IMPORTED_MODULE_3__.CommonModule],
providers: [],
bootstrap: [_app_component__WEBPACK_IMPORTED_MODULE_0__.AppComponent]
})], AppModule);
/***/ }),
/***/ "./app/javascript/ng/my.service.ts":
/*!*****************************************!*\
!*** ./app/javascript/ng/my.service.ts ***!
\*****************************************/
/***/ (function(__unused_webpack_module, __webpack_exports__, __webpack_require__) {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "MyService": function() { return /* binding */ MyService; }
/* harmony export */ });
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! #angular/core */ "./node_modules/#angular/core/fesm2020/core.mjs");
var __decorate = undefined && undefined.__decorate || function (decorators, target, key, desc) {
var c = arguments.length,
r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc,
d;
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
return c > 3 && r && Object.defineProperty(target, key, r), r;
};
let MyService = class MyService {};
MyService = __decorate([(0,_angular_core__WEBPACK_IMPORTED_MODULE_0__.Injectable)({
providedIn: 'root'
})], MyService);
/***/ })
},
/******/ function(__webpack_require__) { // webpackRuntimeModules
/******/ var __webpack_exec__ = function(moduleId) { return __webpack_require__(__webpack_require__.s = moduleId); }
/******/ __webpack_require__.O(0, ["vendors-node_modules_webpack-dev-server_client_index_js_protocol_ws_3A_hostname_localhost_por-54b5bb","vendors-node_modules_zone_js_fesm2015_zone_js-node_modules_angular_platform-browser-dynamic_f-70e316"], function() { return __webpack_exec__("./node_modules/webpack-dev-server/client/index.js?protocol=ws%3A&hostname=localhost&port=3035&pathname=%2Fws&logging=info&reconnect=10"), __webpack_exec__("./app/javascript/entrypoints/main.ts"); });
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ }
]);
//# sourceMappingURL=main.js.map
And compared to the source code generated from webpack via ng serve
"use strict";
(self["webpackChunkmy_app"] = self["webpackChunkmy_app"] || []).push([["main"],{
/***/ 5041:
/*!**********************************!*\
!*** ./src/app/app.component.ts ***!
\**********************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "AppComponent": () => (/* binding */ AppComponent)
/* harmony export */ });
/* harmony import */ var _my_service__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./my.service */ 9676);
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! #angular/core */ 2560);
class AppComponent {
constructor(service) {
this.service = service;
console.log(service);
}
}
AppComponent.ɵfac = function AppComponent_Factory(t) { return new (t || AppComponent)(_angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵdirectiveInject"](_my_service__WEBPACK_IMPORTED_MODULE_0__.MyService)); };
AppComponent.ɵcmp = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵdefineComponent"]({ type: AppComponent, selectors: [["app-root"]], decls: 2, vars: 0, template: function AppComponent_Template(rf, ctx) { if (rf & 1) {
_angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵelementStart"](0, "h1");
_angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵtext"](1, "hello from ng-serve");
_angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵelementEnd"]();
} }, encapsulation: 2 });
/***/ }),
/***/ 6747:
/*!*******************************!*\
!*** ./src/app/app.module.ts ***!
\*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "AppModule": () => (/* binding */ AppModule)
/* harmony export */ });
/* harmony import */ var _angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! #angular/platform-browser */ 4497);
/* harmony import */ var _angular_common__WEBPACK_IMPORTED_MODULE_3__ = __webpack_require__(/*! #angular/common */ 4666);
/* harmony import */ var _app_component__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./app.component */ 5041);
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! #angular/core */ 2560);
class AppModule {
}
AppModule.ɵfac = function AppModule_Factory(t) { return new (t || AppModule)(); };
AppModule.ɵmod = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵdefineNgModule"]({ type: AppModule, bootstrap: [_app_component__WEBPACK_IMPORTED_MODULE_0__.AppComponent] });
AppModule.ɵinj = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵdefineInjector"]({ providers: [], imports: [[
_angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__.BrowserModule,
_angular_common__WEBPACK_IMPORTED_MODULE_3__.CommonModule
]] });
(function () { (typeof ngJitMode === "undefined" || ngJitMode) && _angular_core__WEBPACK_IMPORTED_MODULE_1__["ɵɵsetNgModuleScope"](AppModule, { declarations: [_app_component__WEBPACK_IMPORTED_MODULE_0__.AppComponent], imports: [_angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__.BrowserModule,
_angular_common__WEBPACK_IMPORTED_MODULE_3__.CommonModule] }); })();
/***/ }),
/***/ 9676:
/*!*******************************!*\
!*** ./src/app/my.service.ts ***!
\*******************************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony export */ __webpack_require__.d(__webpack_exports__, {
/* harmony export */ "MyService": () => (/* binding */ MyService)
/* harmony export */ });
/* harmony import */ var _angular_core__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! #angular/core */ 2560);
class MyService {
}
MyService.ɵfac = function MyService_Factory(t) { return new (t || MyService)(); };
MyService.ɵprov = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdefineInjectable"]({ token: MyService, factory: MyService.ɵfac, providedIn: 'root' });
/***/ }),
/***/ 4431:
/*!*********************!*\
!*** ./src/main.ts ***!
\*********************/
/***/ ((__unused_webpack_module, __webpack_exports__, __webpack_require__) => {
__webpack_require__.r(__webpack_exports__);
/* harmony import */ var _angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__ = __webpack_require__(/*! #angular/platform-browser */ 4497);
/* harmony import */ var _app_app_module__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(/*! ./app/app.module */ 6747);
/* harmony import */ var zone_js__WEBPACK_IMPORTED_MODULE_1__ = __webpack_require__(/*! zone.js */ 4946);
/* harmony import */ var zone_js__WEBPACK_IMPORTED_MODULE_1___default = /*#__PURE__*/__webpack_require__.n(zone_js__WEBPACK_IMPORTED_MODULE_1__);
_angular_platform_browser__WEBPACK_IMPORTED_MODULE_2__.platformBrowser().bootstrapModule(_app_app_module__WEBPACK_IMPORTED_MODULE_0__.AppModule).catch(console.error);
/***/ })
},
/******/ __webpack_require__ => { // webpackRuntimeModules
/******/ var __webpack_exec__ = (moduleId) => (__webpack_require__(__webpack_require__.s = moduleId))
/******/ __webpack_require__.O(0, ["vendor"], () => (__webpack_exec__(8202), __webpack_exec__(4431)));
/******/ var __webpack_exports__ = __webpack_require__.O();
/******/ }
]);
//# sourceMappingURL=main.js.map
One of the differences I see is there is a bunch of Reflect stuff in the code generated from webpack with rails. I don't know where this is coming from, as it's no where in the actual code.....
But more importantly, it appears stuff like:
MyService.ɵprov = /*#__PURE__*/ _angular_core__WEBPACK_IMPORTED_MODULE_0__["ɵɵdefineInjectable"]( ... )
is completely missing from the javascript generated from webpacker from the rails app.....
Does anyone have any ideas about what is going on here and how I might troubleshoot this further?
** UPDATE **
Looking at the inject() usage notes in fesm2020/core.mjs says:
In practice the `inject()` calls are allowed in a constructor, a constructor parameter and a field initializer:
So as an experiment, I changed:
...
export class AppComponent {
constructor (private service: MyService) {
}
...
to:
import { inject } from '#angular/core';
...
export class AppComponent {
service: MyService;
constructor () {
this.service = inject(MyService);
}
...
works fine.
WHY can I not get injectables in the constructor function, but I can get them from the injector?!
Related
I've created a library with a directive that injects a service. This library is loaded with a forRoot method in each lazy loaded component where is going to be used.
*** library.module ***
export const SERVICE_INYECTION_TOKEN: InjectionToken<any> = new InjectionToken('service')
export interface IDirectiveModuleConfig {
serviceAdapterConfiguration?: {provider: Provider, moduleName: string};
}
#NgModule({
imports: [
CommonModule
],
declarations: [DirectiveDirective],
exports: [DirectiveDirective]
})
export class LibraryModule {
public static forRoot(config: IDirectiveModuleConfig = {}): ModuleWithProviders<LibraryModule> {
console.log("Library loaded in module " + config.serviceAdapterConfiguration.moduleName)
return {
ngModule: LibraryModule,
providers: [
config.serviceAdapterConfiguration.provider
]
};
}
}
*** directive.directive ***
#Directive({
selector: '[directive]',
})
export class DirectiveDirective implements AfterViewInit {
#Input() methodName: string;
constructor(
private element: ElementRef,
private renderer: Renderer2,
#Inject(SERVICE_INYECTION_TOKEN) private service: any
) {}
ngAfterViewInit(): void {
this.element.nativeElement.innerText += this.service[this.methodName]()
this.renderer.setValue(this.element.nativeElement, this.service[this.methodName]())
}
}
In my main project, I have two lazy-loadeds modules, and each one have a component. One of this modules and its component are lazylodaded by the RouterModules. It works OK
*** app-routing.module ***
const routes: Routes = [
{
path: 'a',
loadChildren: () =>
import('./modules/module-a/module-a.module').then((m) => m.ModuleAModule),
},
{
path: 'b',
loadChildren: () =>
import('./modules/module-b/module-b.module').then((m) => m.ModuleBModule),
},
];
#NgModule({
imports: [RouterModule.forRoot(routes)],
exports: [RouterModule],
})
export class AppRoutingModule {}
The other one is created by compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent() in the parent component. It works ok without the service inection, but when I inject the service I get a NullInjectorError.
*** app.component ***
#Component({
selector: 'app-root',
templateUrl: './app.component.html',
styleUrls: ['./app.component.css'],
})
export class AppComponent {
#ViewChild("viewContainerRef", { read: ViewContainerRef }) viewContainerRef: ViewContainerRef
component = null;
title = 'component-overview';
constructor(private compiler: Compiler, private injector: Injector) {}
async createModuleAndComponetC() {
const componentInjector: Injector = Injector.create({providers:[{provide:'service', useExisting: ServiceCService}]})
this.viewContainerRef.clear()
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0]
const component: ComponentRef<any> = this.viewContainerRef.createComponent(componentFactory);
});
}
}
MODULE A (lazy loaded by routerModule working OK) with its component and service
const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceAService },
moduleName: 'A',
}
};
#NgModule({
imports: [
LibraryModule.forRoot(serviceConfig),
CommonModule,
ModuleARoutingModuleModule,
],
declarations: [ComponentAComponent],
exports: [ComponentAComponent],
})
export class ModuleAModule {
constructor(){
console.log("moduleA loaded")
}
}
#Component({
selector: 'app-component-a',
templateUrl: './component-a.component.html',
styleUrls: ['./component-a.component.css'],
})
export class ComponentAComponent implements OnInit {
constructor() {}
ngOnInit() {}
}
#Injectable({
providedIn: 'root'
})
export class ServiceAService {
constructor() { }
serviceA(){
return(" service A!")
}
}
MODULE C (loaded manually with compileModuleAndAllComponentsAsync() and viewContainerRef.createComponent()
export const serviceConfig: IDirectiveModuleConfig = {
serviceAdapterConfiguration: {
provider: { provide: SERVICE_INYECTION_TOKEN, useClass: ServiceCService },
moduleName: 'C',
},
};
#NgModule({
imports: [CommonModule, LibraryModule.forRoot(serviceConfig)],
declarations: [ComponentCComponent],
})
export class ModuleCModule {
constructor() {
console.log('moduleC loaded');
}
static
}
#Component({
selector: 'app-component-c',
templateUrl: './component-c.component.html',
styleUrls: ['./component-c.component.css'],
providers: [ServiceCService],
})
export class ComponentCComponent implements OnInit {
constructor() {
console.log('component C constructor');
}
ngOnInit() {
console.log('component C OnInit');
}
}
#Injectable({
providedIn: 'root',
})
export class ServiceCService {
constructor() {}
serviceC() {
return ' service C!';
}
}
In this example Modules A and B are used with router outlet, and module C is loaded with Compiler and the component is used in a *ngCompilerOutlet
I think that the problem is in the way I load my ComponentC... but I'm a little bit lost...
In adition... i've founded that the module C create a new instance each time I load this, and is not working like singleton...
stackblitz with the test project
Finally, I got success!
I saw that I could pass an injector to the viewContainerRef. CreateComponent () method. I tried with the same injector I had used to create the module in the noModuleFactory. Create () method, but it was still wrong.
Finally y realized that NgModule class exports an injector, I suposed this injector provide al the providers in this module and it works ok!!
Now my createModuleAndComponetC() is:
async createModuleAndComponetC() {
this.viewContainerRef.clear();
const module = (await import('./modules/module-c/module-c.module'))
.ModuleCModule;
this.compiler.compileModuleAndAllComponentsAsync(module).then((factory) => {
const module = factory.ngModuleFactory.create(this.injector);
const componentFactory = factory.componentFactories[0];
const component: ComponentRef<any> =
this.viewContainerRef.createComponent(
componentFactory,
0,
module.injector
);
});
}
here is the corrected stackbliz
I migrated my Expo SDK 43 Managed Workflow project to an EAS Build.
I use Google Maps as my default map choice for Android and iOS, but I when navigating to a screen with a map I receive this error:
react-native-maps: AirGoogleMaps dir must be added to your xCode project to support GoogleMaps
on iOS. at node_modules/react-native-maps/lib/components/decorateMapComponent.js:27:54 in <anonymous>
When I follow the path laid out in the error to my node_modules file, it takes me to "decorateMapComponent.js" which is a js file that contains the code below.
What needs to be modified to make the EAS on iOS accept Google Maps?
Below is the code:
import PropTypes from 'prop-types';
import { requireNativeComponent, NativeModules, Platform } from 'react-native';
import { PROVIDER_DEFAULT, PROVIDER_GOOGLE } from './ProviderConstants';
export const SUPPORTED = 'SUPPORTED';
export const USES_DEFAULT_IMPLEMENTATION = 'USES_DEFAULT_IMPLEMENTATION';
export const NOT_SUPPORTED = 'NOT_SUPPORTED';
export function getAirMapName(provider) {
if (Platform.OS === 'android') {
return 'AIRMap';
}
if (provider === PROVIDER_GOOGLE) {
return 'AIRGoogleMap';
}
return 'AIRMap';
}
function getAirComponentName(provider, component) {
return `${getAirMapName(provider)}${component}`;
}
export const contextTypes = {
provider: PropTypes.string,
};
export const createNotSupportedComponent = message => () => {
console.error(message);
return null;
};
function getViewManagerConfig(viewManagerName) {
const UIManager = NativeModules.UIManager;
if (!UIManager.getViewManagerConfig) {
// RN < 0.58
return UIManager[viewManagerName];
}
// RN >= 0.58
return UIManager.getViewManagerConfig(viewManagerName);
}
export const googleMapIsInstalled = !!getViewManagerConfig(
getAirMapName(PROVIDER_GOOGLE)
);
export default function decorateMapComponent(
Component,
{ componentType, providers }
) {
const components = {};
const getDefaultComponent = () =>
requireNativeComponent(getAirComponentName(null, componentType), Component);
Component.contextTypes = contextTypes;
Component.prototype.getAirComponent = function getAirComponent() {
const provider = this.context.provider || PROVIDER_DEFAULT;
if (components[provider]) {
return components[provider];
}
if (provider === PROVIDER_DEFAULT) {
components[PROVIDER_DEFAULT] = getDefaultComponent();
return components[PROVIDER_DEFAULT];
}
const providerInfo = providers[provider];
const platformSupport = providerInfo[Platform.OS];
const componentName = getAirComponentName(provider, componentType);
if (platformSupport === NOT_SUPPORTED) {
components[provider] = createNotSupportedComponent(
`react-native-maps: ${componentName} is not supported on ${Platform.OS}`
);
} else if (platformSupport === SUPPORTED) {
if (
provider !== PROVIDER_GOOGLE ||
(Platform.OS === 'ios' && googleMapIsInstalled)
) {
components[provider] = requireNativeComponent(componentName, Component);
}
} else {
// (platformSupport === USES_DEFAULT_IMPLEMENTATION)
if (!components[PROVIDER_DEFAULT]) {
components[PROVIDER_DEFAULT] = getDefaultComponent();
}
components[provider] = components[PROVIDER_DEFAULT];
}
return components[provider];
};
Component.prototype.getUIManagerCommand = function getUIManagerCommand(name) {
const componentName = getAirComponentName(
this.context.provider,
componentType
);
return getViewManagerConfig(componentName).Commands[name];
};
Component.prototype.getMapManagerCommand = function getMapManagerCommand(
name
) {
const airComponentName = `${getAirComponentName(
this.context.provider,
componentType
)}Manager`;
return NativeModules[airComponentName][name];
};
return Component;
}
try adding "expoCli": "4.13.0", to your eas.json like this:
"cli": {
"version": ">= 0.41.0"
},
"build": {
"base": {
"expoCli": "4.13.0",
},
"simulator": {
"extends": "base",
"android": {
"buildType": "apk"
},
"ios": {
"simulator": true
}
},
"development": {
"extends": "base",
"developmentClient": true,
"distribution": "internal"
},
"preview": {
"extends": "base",
"distribution": "internal"
},
"production": {
"extends": "base",
"releaseChannel": "production"
}
},
"submit": {
}
}
Had same issue, did work for me.
Update 1: ============
After remove "window.ShadyDOM={force:true};" then it worked. But this is causing other problem ;):
https://vaadin.com/forum/thread/17399734/leverage-browser-save-password-feature
#Override
public void configurePage(InitialPageSettings settings) {
// TODO Auto-generated method stub
settings.addMetaTag("mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-capable", "yes");
settings.addMetaTag("apple-mobile-web-app-status-bar-style", "black");
settings.addInlineWithContents(
InitialPageSettings.Position.PREPEND, "window.customElements=window.customElements||{};"
+ "window.customElements.forcePolyfill=true;" + "window.ShadyDOM={force:true};",
InitialPageSettings.WrapMode.JAVASCRIPT);
}
End Update. ============
I am trying to integrate Payal checkout to Vaadin 14.7 (Spring core, not spring boot).
Here is paypal-view.js
import { LitElement, html, css } from "lit-element";
let buttons;
let hasRendered = false;
class PaypalElement extends LitElement {
static get properties() {
return {
mood: {
type: String,
noAccessor: false,
hasChanged(newVal, oldVal) {
console.log("newVal " + newVal + " oldVal " + oldVal);
},
},
};
}
static get styles() {
return [
css`
mood_color {
color: green;
}
#paypal-button {
size: "responsive";
}
`,
];
}
firstUpdated(_changedProperties) {
let testFname = this.shadowRoot.getElementById("fname");
super.firstUpdated(_changedProperties);
if (buttons && buttons.close && hasRendered) {
buttons.close();
hasRendered = false;
}
buttons = window.paypal.Buttons({
// Set up the transaction
createOrder: function (data, actions) {
return actions.order.create({
application_context: {
brand_name: "Brand name",
user_action: "PAY_NOW",
//No shipping for in-tangible merchant
shipping_preference: "NO_SHIPPING",
payment_method: {
payee_preferred: "IMMEDIATE_PAYMENT_REQUIRED", // Pending status transactions will not be allowed if you pass this parameter.
payer_selected: "PAYPAL",
},
},
purchase_units: [
{
soft_descriptor: "CC_STATEMENT_NAME",
amount: {
value: "5.00",
},
},
],
});
},
// Finalize the transaction
onApprove: function (data, actions) {
const elementText = document.getElementById("fname");
return actions.order.capture().then(function (orderData) {
// Successful capture! For demo purposes:
console.log(
"Capture result",
orderData,
JSON.stringify(orderData, null, 2)
);
let transaction = orderData.purchase_units[0].payments.captures[0];
// Replace the above to show a success message within this page, e.g.
// const element = document.getElementById('paypal-button-container');
// element.innerHTML = '';
// element.innerHTML = '<h3>Thank you for your payment!</h3>';
// Or go to another URL: actions.redirect('thank_you.html');
document.getElementById("update-paypal-trans").innerHTML =
"update-paypal-trans = " + transaction.id;
// trigger lit event
testFname.value = transaction.id;
testFname.click();
document.getElementById("paypalelement").remove();
//console.log(elementText);
});
},
onError: function (error) {
console.log("onError", error, JSON.stringify(error, null, 2));
},
onCancel: function (data, actions) {
console.log("onCancel", data, JSON.stringify(data, null, 2));
document.getElementById("update-paypal-trans").innerHTML =
"testing 123";
// update shadow element
testFname.value = "12345 " + actions;
// trigger lit event
testFname.click();
},
});
// load paypal buttons and put them to element id="paypal-button-to-display" which is shadow dom
buttons
.render(this.shadowRoot.getElementById("paypal-button-to-display"))
.then(() => {
hasRendered = true;
})
.catch((err) => {
console.log(err)
// not mounted anymore, we can safely ignore the error
return;
});
}
// outside updates shadow element
updateShadow() {
this.shadowRoot.getElementById("test-update-shadow").innerHTML =
"test update shadow trans";
this.mood = "nice";
}
updateTask(e) {
console.log("updateTask: " + e);
}
updateTaskClick(e) {
console.log("updateTaskClick: " + e);
// call back-end
}
render() {
return html`
Web Components are
<span class="mood_color"> ${this.mood} and ${this.innerHTML}</span>!
<input
type="text"
id="fname"
name="fname"
value="${this.mood}"
#change=${(e) => this.updateTask(e.target.value)}
#click="${(e) => this.updateTaskClick(e.target.value)}"
/>
<div id="paypal-button-to-display"></div>
<br />
<div id="test-update-shadow">test-update-shadow-default</div>
<br />
<input
#click="${() => this.updateShadow()}"
id="myinput"
type="button"
value="update shadow button"
/>
`;
}
attributeChangedCallback(name, oldval, newval) {
super.attributeChangedCallback(name, oldval, newval);
console.log("attribute change: ", name, newval);
}
changeProperties() {
let randomString = Math.floor(Math.random() * 100).toString();
this.mood = "myProp " + randomString;
console.log("randomString change: ", randomString);
}
}
customElements.define("paypal-element", PaypalElement);
hello-world-view.ts
import { html, LitElement, customElement } from 'lit-element';
import './paypal-view';
import '#vaadin/vaadin-button';
import '#vaadin/vaadin-text-field';
#customElement('hello-world-view')
export class HelloWorldView extends LitElement {
createRenderRoot() {
// Do not use a shadow root
return this;
}
constructor() {
super();
}
render() {
return html`
<vaadin-text-field id="name" label="Your name"></vaadin-text-field>
<vaadin-button >Say hello</vaadin-button>
<paypal-elementt mood="great" id="paypalBut">hello customer</paypal-elementt>
`;
}
}
hello-world-view.ts
package com.lecompany.iad.view;
import com.lecompany.iad.app.MainView;
import com.vaadin.flow.component.Tag;
import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.dependency.JsModule;
import com.vaadin.flow.component.littemplate.LitTemplate;
import com.vaadin.flow.component.notification.Notification;
import com.vaadin.flow.component.template.Id;
import com.vaadin.flow.component.textfield.TextField;
import com.vaadin.flow.router.PageTitle;
import com.vaadin.flow.router.Route;
import com.vaadin.flow.router.RouteAlias;
/**
* A Designer generated component for the stub-tag template.
*
* Designer will add and remove fields with #Id mappings but does not overwrite
* or otherwise change this file.
*/
#PageTitle("Hello World")
#Route(value = "hello", layout = MainView.class)
#Tag("hello-world-view")
#JsModule("./src/view/hello-world-view.ts")
public class HelloWorldView extends LitTemplate {
#Id
private TextField name;
// #Id
// private Button sayHello;
public HelloWorldView() {
UI.getCurrent().getPage().addJavaScript("https://www.paypal.com/sdk/js?client-id=AejZZjQsvxS299I2_LSnRkJStp0AsBzScCqbGK1_W6RNJssNR5NVnKYd97dM2kOJnRMF1u1ldLGjOlZ5¤cy=USD");
// sayHello.addClickListener(e -> {
// Notification.show("Hello " + name.getValue());
// });
}
}
And here is error:
I tested above codes in Spring boot, then it works fine.
but it got error in the normal spring code. Any advice on this ?
There is reported issue for Paypal.
Paypal reported issue
It is a Vaadin bug and reported here:
Vaadin bug
I have built a geolocation service that I wish to use the getUserPosition() method on the home.page
location.service.ts
import { Injectable } from '#angular/core';
import { Geolocation, GeolocationOptions, Geoposition, PositionError } from '#ionic-native/geolocation/ngx';
#Injectable({
providedIn: 'root'
})
export class LocationService {
options: GeolocationOptions;
currentPos: Geoposition;
loc: any;
constructor( private geolocation: Geolocation ) { }
getUserPosition() {
return new Promise((resolve, reject) => {
this.options = {
maximumAge: 3000,
enableHighAccuracy: true
};
this.geolocation.getCurrentPosition(this.options).then((pos: Geoposition) => {
this.currentPos = pos;
const location = {
lat: pos.coords.latitude,
lng: pos.coords.longitude,
time: new Date(),
};
console.log('loc', location);
resolve(pos);
}, (err: PositionError) => {
console.log("error : " + err.message);
reject(err.message);
});
});
}
}
I access the service in my home.page
home.ts
import { Component, OnInit } from '#angular/core';
import { LocationService } from '../../services/geolocation/location.service';
#Component({
selector: 'app-home',
templateUrl: './home.page.html',
styleUrls: ['./home.page.scss'],
})
export class WorkalonePage implements OnInit {
getPosition: any;
constructor(
private LocationService: LocationService
) {}
ngOnInit() {
this.getPosition = this.LocationService.getUserPosition;
}
}
home.html
<ion-content>
<ion-button expand="full" color="primary" (click)="getUserPosition()">Get Location Sevice</ion-button>
</ion-content>
but when I click on (click)="getUserPosition()" on my html I get the error getUserPosition() is not a function. I have have been looking online for answers, but all answers involve using the geolocation in the home.ts file. Any help would be greatly appreciated.
From what I see in home.ts, you haven't defined a function named getUserPosition.
Add something along the lines of what's below to home.ts:
getUserPosition() {
this.LocationService.getUserPosition().then((pos) => {
this.getPosition = pos;
});
}
You can directly call your service method in html like this
LocationService.ts
getUserPosition() {
this.LocationService.getUserPosition().then((pos) => {
this.getPosition = pos;
});
}
Define service object in home.ts
constructor(public ls:LocationService){}
At home.html
<ion-button expand="full" color="primary" (click)="ls.getUserPosition()">
Get Location Sevice
</ion-button>
I'm using angular 2 and in this part (home page) I have a google autocomplete place input that if you click it goes another page(map) and it shows the map with that latitude and longitude user clicked previous page. However the point is I want in the URL, lat and long will be shown.It's complicated for me and don't know what to do,also I have read https://angular.io/tutorial/toh-pt5#parameterized-route but didn't understand.
app-routing.module:
import { NgModule } from '#angular/core';
import { RouterModule, Routes } from '#angular/router';
import { HomeComponent } from './home/home.component';
import { MapComponent } from './search/map.component';
const routes: Routes = [
{ path: '', redirectTo: '/home', pathMatch: 'full' },
{ path: 'home', component: HomeComponent },
{ path: 'map/'+this.latitude+','+this.longitude, component:
MapComponent },
];
#NgModule({
imports: [ RouterModule.forRoot(routes) ],
exports: [ RouterModule ]
})
export class AppRoutingModule {}
in the path I wrote like this but don't know is correct or not.
home.html:
<div class="col-md-8 col-md-push-2 container search-location">
<div class="form-group">
<input id="search" placeholder="select place"
autocorrect="off" autocapitalize="off" spellcheck="off" type="text"
class="form-control"
#search [formControl]="searchControl">
</div>
</div>
home.component.ts:
import { Component, ElementRef, NgModule, NgZone, OnInit, ViewChild } from
'#angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from
"#angular/forms";
import { BrowserModule } from "#angular/platform-browser";
import { AgmCoreModule, MapsAPILoader } from '#agm/core';
import { Router } from '#angular/router';
import {} from '#types/googlemaps';
declare let google: any;
import 'rxjs/add/observable/of';
#Component({
selector: 'home-search',
templateUrl: './home-search.html',
styles: [`
agm-map {
height: 300px;
}
`],
})
export class HomeSearchComponent implements OnInit {
public google:any;
public latitude: number;
public longitude: number;
public searchControl: FormControl;
public zoom: number;
#ViewChild("search")
public searchElementRef: ElementRef;
constructor(
private mapsAPILoader: MapsAPILoader,
private ngZone: NgZone,
private router:Router,
) {}
ngOnInit() {
let options = {
types: ["address"],
componentRestrictions: {country: "ir"}
};
//set google maps defaults
this.zoom = 4;
this.latitude = 39.8282;
this.longitude = -98.5795;
//create search FormControl
this.searchControl = new FormControl();
//set current position
this.setCurrentPosition();
//load Places Autocomplete
this.mapsAPILoader.load().then(() => {
let autocomplete = new
google.maps.places.Autocomplete(this.searchElementRef.nativeElement,
{options});
autocomplete.addListener("place_changed", () => {
this.ngZone.run(() => {
//get the place result
let place: google.maps.places.PlaceResult = autocomplete.getPlace();
//verify result
if (place.geometry === undefined || place.geometry === null) {
return;
}
//set latitude, longitude and zoom
this.latitude = place.geometry.location.lat();
this.longitude = place.geometry.location.lng();
this.zoom = 12;
this.router.navigate(['/map/'+this.latitude+','+this.longitude]);
});
});
});
}
private setCurrentPosition() {
if ("geolocation" in navigator) {
navigator.geolocation.getCurrentPosition((position) => {
this.latitude = position.coords.latitude;
this.longitude = position.coords.longitude;
this.zoom = 12;
});
}
}
}
map.html:
<div class="col-md-8 col-md-push-2 container search-location">
<agm-map [latitude]="latitude" [longitude]="longitude" [scrollwheel]="true"
[zoom]="zoom">
<agm-marker [latitude]="latitude" [longitude]="longitude"></agm-marker>
</agm-map>
</div>
map.component.ts:
import { Component, ElementRef, NgModule, NgZone, OnInit, ViewChild } from
'#angular/core';
import { FormControl, FormsModule, ReactiveFormsModule } from
"#angular/forms";
import { BrowserModule } from "#angular/platform-browser";
import { AgmCoreModule, MapsAPILoader } from '#agm/core';
import { Router } from '#angular/router';
import {} from '#types/googlemaps';
declare let google: any;
#Component({
selector: 'map',
templateUrl: './map.html',
styles: [`
agm-map {
height: 300px;
}
`],
})
export class MapComponent implements OnInit{
private google:any;
private latitude: number;
private longitude: number;
private searchControl: FormControl;
private zoom: number;
ngOnInit(){
}
}
It relates to parameters but I don't know how to write the code.
Could you please help me,I'm confused.
Thank you.