Skip to content

feat: support standalone components bootstrap #108

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/nativescript-demo-ng/project.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,9 @@
"android": {
"platform": "android"
},
"ios": {}
"ios": {
"platform": "ios"
}
}
}
},
Expand Down
47 changes: 24 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,47 +35,47 @@
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
},
"dependencies": {
"@angular/animations": "15.2.1",
"@angular/common": "15.2.1",
"@angular/compiler": "15.2.1",
"@angular/core": "15.2.1",
"@angular/forms": "15.2.1",
"@angular/platform-browser": "15.2.1",
"@angular/platform-browser-dynamic": "15.2.1",
"@angular/router": "15.2.1",
"@angular/animations": "15.2.2",
"@angular/common": "15.2.2",
"@angular/compiler": "15.2.2",
"@angular/core": "15.2.2",
"@angular/forms": "15.2.2",
"@angular/platform-browser": "15.2.2",
"@angular/platform-browser-dynamic": "15.2.2",
"@angular/router": "15.2.2",
"@nativescript/core": "~8.4.0",
"@nativescript/theme": "~3.0.2",
"@ngx-translate/core": "~14.0.0",
"@nrwl/nx-cloud": "15.1.1",
"@nrwl/nx-cloud": "15.2.1",
"nativescript-ngx-fonticon": "~7.0.0",
"rxjs": "^7.5.6",
"zone.js": "0.12.0"
},
"devDependencies": {
"@angular-devkit/build-angular": "15.2.1",
"@angular-devkit/core": "15.2.1",
"@angular-devkit/schematics": "15.2.1",
"@angular-devkit/build-angular": "15.2.2",
"@angular-devkit/core": "15.2.2",
"@angular-devkit/schematics": "15.2.2",
"@angular-eslint/eslint-plugin": "15.2.1",
"@angular-eslint/eslint-plugin-template": "15.2.1",
"@angular-eslint/template-parser": "15.2.1",
"@angular/compiler-cli": "15.2.1",
"@angular/compiler-cli": "15.2.2",
"@jsdevtools/coverage-istanbul-loader": "3.0.5",
"@nativescript/types": "~8.4.0",
"@nativescript/unit-test-runner": "^3.0.4",
"@nativescript/webpack": "~5.0.12",
"@nrwl/angular": "15.8.5",
"@nrwl/cli": "15.8.5",
"@nrwl/eslint-plugin-nx": "15.8.5",
"@nrwl/jest": "15.8.5",
"@nrwl/js": "15.8.5",
"@nrwl/linter": "15.8.5",
"@nrwl/node": "15.8.5",
"@nrwl/workspace": "15.8.5",
"@nrwl/angular": "15.8.6",
"@nrwl/cli": "15.8.6",
"@nrwl/eslint-plugin-nx": "15.8.6",
"@nrwl/jest": "15.8.6",
"@nrwl/js": "15.8.6",
"@nrwl/linter": "15.8.6",
"@nrwl/node": "15.8.6",
"@nrwl/workspace": "15.8.6",
"@nstudio/angular": "15.0.3",
"@nstudio/nativescript": "15.0.3",
"@nstudio/nativescript-angular": "15.0.3",
"@nstudio/xplat": "15.0.3",
"@schematics/angular": "15.2.1",
"@schematics/angular": "15.2.2",
"@types/jasmine": "4.3.0",
"@types/jest": "29.4.0",
"@types/node": "^18.7.13",
Expand All @@ -98,7 +98,7 @@
"karma-sinon": "^1.0.5",
"lint-staged": "^13.0.3",
"ng-packagr": "15.2.2",
"nx": "15.8.5",
"nx": "15.8.6",
"nyc": "15.1.0",
"postcss": "^8.4.16",
"postcss-import": "14.1.0",
Expand All @@ -123,3 +123,4 @@
]
}
}

2 changes: 1 addition & 1 deletion packages/angular/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@nativescript/angular",
"version": "15.0.2",
"version": "15.2.0-dev.1",
"homepage": "https://nativescript.org/",
"repository": {
"type": "git",
Expand Down
73 changes: 56 additions & 17 deletions packages/angular/src/lib/application.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import { NgModuleRef, NgZone, PlatformRef } from '@angular/core';
import { ApplicationRef, EnvironmentProviders, NgModuleRef, NgZone, PlatformRef, Provider, Type, ɵinternalCreateApplication as internalCreateApplication } from '@angular/core';
import { filter, map, take } from 'rxjs/operators';
import { Application, ApplicationEventData, Color, LaunchEventData, LayoutBase, profile, removeTaggedAdditionalCSS, StackLayout, TextView, View, Utils, Trace } from '@nativescript/core';
import { AppHostView } from './app-host-view';
import { NativeScriptLoadingService } from './loading.service';
import { APP_ROOT_VIEW, DISABLE_ROOT_VIEW_HANDLING, NATIVESCRIPT_ROOT_MODULE_ID } from './tokens';
import { Observable, Subject } from 'rxjs';
import { NativeScriptDebug } from './trace';
import { NATIVESCRIPT_MODULE_PROVIDERS, NATIVESCRIPT_MODULE_STATIC_PROVIDERS } from './nativescript';

export interface AppLaunchView extends LayoutBase {
// called when the animation is to begin
Expand All @@ -27,7 +28,7 @@ export type NgModuleReason = 'hotreload' | 'applaunch' | 'appexit';
export type NgModuleEvent =
| {
moduleType: 'main' | 'loading' | string;
reference: NgModuleRef<unknown>;
reference: NgModuleRef<unknown> | ApplicationRef;
reason: NgModuleReason | string;
}
| {
Expand Down Expand Up @@ -57,9 +58,29 @@ export const onAfterLivesync: Observable<{
map((v) => ({ moduleRef: v.reference as NgModuleRef<any> }))
);
export interface AppRunOptions<T, K> {
appModuleBootstrap: (reason: NgModuleReason) => Promise<NgModuleRef<T>>;
loadingModule?: (reason: NgModuleReason) => Promise<NgModuleRef<K>>;
/**
* Runs when the app is launched or during HMR.
* May not run immediately if the app was started in background (e.g. push notification)
* @param reason reason for bootstrap. @see {NgModuleReason}
* @returns Promise to the bootstrapped NgModuleRef
*/
appModuleBootstrap: (reason: NgModuleReason) => Promise<NgModuleRef<T> | ApplicationRef>;
/**
* Loads a custom NgModule for the loading screen.
* This loads only if appModuleBootstrap doesn't resolve synchronously (e.g. async APP_INITIALIZER).
* @param reason reason for bootstrap. @see {NgModuleReason}
* @returns Promise to the bootstrapped NgModuleRef. Must resolve immediately (no async initialization)
*/
loadingModule?: (reason: NgModuleReason) => Promise<NgModuleRef<K> | ApplicationRef>;
/**
* Simpler than loadingModule, this will show a view while the app is bootstrapping asynchronously.
* @param reason reason for bootstrap. @see {NgModuleReason}
* @returns View that will be shown while app boots
*/
launchView?: (reason: NgModuleReason) => AppLaunchView;
/**
* Wether we are running in an embedded context (e.g. embedding NativeScript in an existing app)
*/
embedded?: boolean;
}

Expand All @@ -71,17 +92,17 @@ if (import.meta['webpackHot']) {
};
}

function emitModuleBootstrapEvent<T>(ref: NgModuleRef<T>, name: 'main' | 'loading', reason: NgModuleReason) {
function emitModuleBootstrapEvent<T>(ref: NgModuleRef<T> | ApplicationRef, name: 'main' | 'loading', reason: NgModuleReason) {
postAngularBootstrap$.next({
moduleType: name,
reference: ref,
reason,
});
}

function destroyRef<T>(ref: NgModuleRef<T>, name: 'main' | 'loading', reason: NgModuleReason): void;
function destroyRef<T>(ref: NgModuleRef<T> | ApplicationRef, name: 'main' | 'loading', reason: NgModuleReason): void;
function destroyRef(ref: PlatformRef, reason: NgModuleReason): void;
function destroyRef<T>(ref: PlatformRef | NgModuleRef<T>, name?: string, reason?: string): void {
function destroyRef<T>(ref: PlatformRef | ApplicationRef | NgModuleRef<T>, name?: string, reason?: string): void {
if (ref) {
if (ref instanceof PlatformRef) {
preAngularDisposal$.next({
Expand All @@ -90,7 +111,7 @@ function destroyRef<T>(ref: PlatformRef | NgModuleRef<T>, name?: string, reason?
reason: name,
});
}
if (ref instanceof NgModuleRef) {
if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
preAngularDisposal$.next({
moduleType: name,
reference: ref,
Expand Down Expand Up @@ -177,12 +198,29 @@ function runSynchronously(fn: () => void, done?: () => void): void {
}
}

function createProvidersConfig(options?: ApplicationConfig) {
return {
appProviders: [...NATIVESCRIPT_MODULE_PROVIDERS, ...NATIVESCRIPT_MODULE_STATIC_PROVIDERS, ...(options?.providers ?? [])],
// platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS
};
}

export interface ApplicationConfig {
/**
* List of providers that should be available to the root component and all its children.
*/
providers: Array<Provider | EnvironmentProviders>;
}
export function bootstrapApplication(rootComponent: Type<unknown>, options?: ApplicationConfig): Promise<ApplicationRef> {
return internalCreateApplication({ rootComponent, ...createProvidersConfig(options) });
}

export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
let mainModuleRef: NgModuleRef<T> = null;
let loadingModuleRef: NgModuleRef<K>;
let mainModuleRef: NgModuleRef<T> | ApplicationRef = null;
let loadingModuleRef: NgModuleRef<K> | ApplicationRef;
let platformRef: PlatformRef = null;
let bootstrapId = -1;
const updatePlatformRef = (moduleRef: NgModuleRef<T | K>, reason: NgModuleReason) => {
const updatePlatformRef = (moduleRef: NgModuleRef<T | K> | ApplicationRef, reason: NgModuleReason) => {
const newPlatformRef = moduleRef.injector.get(PlatformRef);
if (newPlatformRef === platformRef) {
return;
Expand All @@ -193,12 +231,12 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
};
let launchEventDone = true;
let targetRootView: View = null;
const setRootView = (ref: NgModuleRef<T | K> | View) => {
const setRootView = (ref: NgModuleRef<T | K> | ApplicationRef | View) => {
if (bootstrapId === -1) {
// treat edge cases
return;
}
if (ref instanceof NgModuleRef) {
if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
if (ref.injector.get(DISABLE_ROOT_VIEW_HANDLING, false)) {
return;
}
Expand Down Expand Up @@ -262,10 +300,11 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
return;
}
mainModuleRef = ref;
ref.onDestroy(() => (mainModuleRef = mainModuleRef === ref ? null : mainModuleRef));

(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(() => (mainModuleRef = mainModuleRef === ref ? null : mainModuleRef));
updatePlatformRef(ref, reason);
const styleTag = ref.injector.get(NATIVESCRIPT_ROOT_MODULE_ID);
ref.onDestroy(() => {
(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(() => {
removeTaggedAdditionalCSS(styleTag);
});
bootstrapped = true;
Expand Down Expand Up @@ -296,10 +335,10 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
return;
}
loadingModuleRef = loadingRef;
loadingModuleRef.onDestroy(() => (loadingModuleRef = loadingModuleRef === loadingRef ? null : loadingModuleRef));
(loadingModuleRef instanceof ApplicationRef ? loadingModuleRef.components[0] : loadingModuleRef).onDestroy(() => (loadingModuleRef = loadingModuleRef === loadingRef ? null : loadingModuleRef));
updatePlatformRef(loadingRef, reason);
const styleTag = loadingModuleRef.injector.get(NATIVESCRIPT_ROOT_MODULE_ID);
loadingRef.onDestroy(() => {
(loadingModuleRef instanceof ApplicationRef ? loadingModuleRef.components[0] : loadingModuleRef).onDestroy(() => {
removeTaggedAdditionalCSS(styleTag);
});
setRootView(loadingRef);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Directive, DoCheck, ElementRef, EmbeddedViewRef, EventEmitter, forwardRef, Host, HostListener, inject, Inject, InjectionToken, Input, IterableDiffer, IterableDiffers, NgZone, OnDestroy, Output, TemplateRef, ViewChild, ViewContainerRef, ɵisListLikeIterable as isListLikeIterable } from '@angular/core';
import { AfterContentInit, ChangeDetectionStrategy, ChangeDetectorRef, Component, ContentChild, Directive, DoCheck, ElementRef, EmbeddedViewRef, EventEmitter, forwardRef, Host, HostListener, inject, Inject, InjectionToken, Input, IterableDiffer, IterableDiffers, NgZone, OnDestroy, Output, TemplateRef, ViewChild, ViewContainerRef } from '@angular/core';
import { ItemEventData, KeyedTemplate, LayoutBase, ListView, ObservableArray, profile, View } from '@nativescript/core';

import { extractSingleViewRecursive } from '../../element-registry/registry';
import { NativeScriptDebug } from '../../trace';
import { NgViewTemplate } from '../../view-refs';
import { isListLikeIterable } from '../../utils/general';

const NG_VIEW = '_ngViewRef';

Expand Down
2 changes: 1 addition & 1 deletion packages/angular/src/lib/public_api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export * from './nativescript-common.module';
export * from './loading.service';
export * from './detached-loader-utils';
// export * from './router/router.module';
export { AppLaunchView, AppRunOptions, NgModuleEvent, NgModuleReason, disableRootViewHanding, onAfterLivesync, onBeforeLivesync, postAngularBootstrap$, preAngularDisposal$, runNativeScriptAngularApp } from './application';
export { AppLaunchView, AppRunOptions, NgModuleEvent, NgModuleReason, disableRootViewHanding, onAfterLivesync, onBeforeLivesync, postAngularBootstrap$, preAngularDisposal$, runNativeScriptAngularApp, ApplicationConfig, bootstrapApplication } from './application';
export * from './element-registry';
export * from './nativescript-xhr-factory';
export { EmulatedRenderer, NativeScriptRendererFactory, COMPONENT_VARIABLE as ɵCOMPONENT_VARIABLE, CONTENT_ATTR as ɵCONTENT_ATTR, HOST_ATTR as ɵHOST_ATTR } from './nativescript-renderer';
Expand Down
11 changes: 11 additions & 0 deletions packages/angular/src/lib/utils/general.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,3 +32,14 @@ export function once(fn: Function) {
export interface ComponentType<T> {
new (...args: any[]): T;
}

export function isListLikeIterable(obj: any): boolean {
if (!isJsObject(obj)) return false;
return Array.isArray(obj) ||
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
Symbol.iterator in obj); // JS Iterable have a Symbol.iterator prop
}

export function isJsObject(o: any): boolean {
return o !== null && (typeof o === 'function' || typeof o === 'object');
}
Loading