Skip to content

Commit 55eb884

Browse files
authored
feat: support standalone components bootstrap (#108)
1 parent 0a7f2d3 commit 55eb884

File tree

8 files changed

+308
-254
lines changed

8 files changed

+308
-254
lines changed

apps/nativescript-demo-ng/project.json

+3-1
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,9 @@
8080
"android": {
8181
"platform": "android"
8282
},
83-
"ios": {}
83+
"ios": {
84+
"platform": "ios"
85+
}
8486
}
8587
}
8688
},

package.json

+24-23
Original file line numberDiff line numberDiff line change
@@ -35,47 +35,47 @@
3535
"changelog": "conventional-changelog -p angular -i CHANGELOG.md -s"
3636
},
3737
"dependencies": {
38-
"@angular/animations": "15.2.1",
39-
"@angular/common": "15.2.1",
40-
"@angular/compiler": "15.2.1",
41-
"@angular/core": "15.2.1",
42-
"@angular/forms": "15.2.1",
43-
"@angular/platform-browser": "15.2.1",
44-
"@angular/platform-browser-dynamic": "15.2.1",
45-
"@angular/router": "15.2.1",
38+
"@angular/animations": "15.2.2",
39+
"@angular/common": "15.2.2",
40+
"@angular/compiler": "15.2.2",
41+
"@angular/core": "15.2.2",
42+
"@angular/forms": "15.2.2",
43+
"@angular/platform-browser": "15.2.2",
44+
"@angular/platform-browser-dynamic": "15.2.2",
45+
"@angular/router": "15.2.2",
4646
"@nativescript/core": "~8.4.0",
4747
"@nativescript/theme": "~3.0.2",
4848
"@ngx-translate/core": "~14.0.0",
49-
"@nrwl/nx-cloud": "15.1.1",
49+
"@nrwl/nx-cloud": "15.2.1",
5050
"nativescript-ngx-fonticon": "~7.0.0",
5151
"rxjs": "^7.5.6",
5252
"zone.js": "0.12.0"
5353
},
5454
"devDependencies": {
55-
"@angular-devkit/build-angular": "15.2.1",
56-
"@angular-devkit/core": "15.2.1",
57-
"@angular-devkit/schematics": "15.2.1",
55+
"@angular-devkit/build-angular": "15.2.2",
56+
"@angular-devkit/core": "15.2.2",
57+
"@angular-devkit/schematics": "15.2.2",
5858
"@angular-eslint/eslint-plugin": "15.2.1",
5959
"@angular-eslint/eslint-plugin-template": "15.2.1",
6060
"@angular-eslint/template-parser": "15.2.1",
61-
"@angular/compiler-cli": "15.2.1",
61+
"@angular/compiler-cli": "15.2.2",
6262
"@jsdevtools/coverage-istanbul-loader": "3.0.5",
6363
"@nativescript/types": "~8.4.0",
6464
"@nativescript/unit-test-runner": "^3.0.4",
6565
"@nativescript/webpack": "~5.0.12",
66-
"@nrwl/angular": "15.8.5",
67-
"@nrwl/cli": "15.8.5",
68-
"@nrwl/eslint-plugin-nx": "15.8.5",
69-
"@nrwl/jest": "15.8.5",
70-
"@nrwl/js": "15.8.5",
71-
"@nrwl/linter": "15.8.5",
72-
"@nrwl/node": "15.8.5",
73-
"@nrwl/workspace": "15.8.5",
66+
"@nrwl/angular": "15.8.6",
67+
"@nrwl/cli": "15.8.6",
68+
"@nrwl/eslint-plugin-nx": "15.8.6",
69+
"@nrwl/jest": "15.8.6",
70+
"@nrwl/js": "15.8.6",
71+
"@nrwl/linter": "15.8.6",
72+
"@nrwl/node": "15.8.6",
73+
"@nrwl/workspace": "15.8.6",
7474
"@nstudio/angular": "15.0.3",
7575
"@nstudio/nativescript": "15.0.3",
7676
"@nstudio/nativescript-angular": "15.0.3",
7777
"@nstudio/xplat": "15.0.3",
78-
"@schematics/angular": "15.2.1",
78+
"@schematics/angular": "15.2.2",
7979
"@types/jasmine": "4.3.0",
8080
"@types/jest": "29.4.0",
8181
"@types/node": "^18.7.13",
@@ -98,7 +98,7 @@
9898
"karma-sinon": "^1.0.5",
9999
"lint-staged": "^13.0.3",
100100
"ng-packagr": "15.2.2",
101-
"nx": "15.8.5",
101+
"nx": "15.8.6",
102102
"nyc": "15.1.0",
103103
"postcss": "^8.4.16",
104104
"postcss-import": "14.1.0",
@@ -123,3 +123,4 @@
123123
]
124124
}
125125
}
126+

packages/angular/package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@nativescript/angular",
3-
"version": "15.0.2",
3+
"version": "15.2.0-dev.1",
44
"homepage": "https://nativescript.org/",
55
"repository": {
66
"type": "git",

packages/angular/src/lib/application.ts

+56-17
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
1-
import { NgModuleRef, NgZone, PlatformRef } from '@angular/core';
1+
import { ApplicationRef, EnvironmentProviders, NgModuleRef, NgZone, PlatformRef, Provider, Type, ɵinternalCreateApplication as internalCreateApplication } from '@angular/core';
22
import { filter, map, take } from 'rxjs/operators';
33
import { Application, ApplicationEventData, Color, LaunchEventData, LayoutBase, profile, removeTaggedAdditionalCSS, StackLayout, TextView, View, Utils, Trace } from '@nativescript/core';
44
import { AppHostView } from './app-host-view';
55
import { NativeScriptLoadingService } from './loading.service';
66
import { APP_ROOT_VIEW, DISABLE_ROOT_VIEW_HANDLING, NATIVESCRIPT_ROOT_MODULE_ID } from './tokens';
77
import { Observable, Subject } from 'rxjs';
88
import { NativeScriptDebug } from './trace';
9+
import { NATIVESCRIPT_MODULE_PROVIDERS, NATIVESCRIPT_MODULE_STATIC_PROVIDERS } from './nativescript';
910

1011
export interface AppLaunchView extends LayoutBase {
1112
// called when the animation is to begin
@@ -27,7 +28,7 @@ export type NgModuleReason = 'hotreload' | 'applaunch' | 'appexit';
2728
export type NgModuleEvent =
2829
| {
2930
moduleType: 'main' | 'loading' | string;
30-
reference: NgModuleRef<unknown>;
31+
reference: NgModuleRef<unknown> | ApplicationRef;
3132
reason: NgModuleReason | string;
3233
}
3334
| {
@@ -57,9 +58,29 @@ export const onAfterLivesync: Observable<{
5758
map((v) => ({ moduleRef: v.reference as NgModuleRef<any> }))
5859
);
5960
export interface AppRunOptions<T, K> {
60-
appModuleBootstrap: (reason: NgModuleReason) => Promise<NgModuleRef<T>>;
61-
loadingModule?: (reason: NgModuleReason) => Promise<NgModuleRef<K>>;
61+
/**
62+
* Runs when the app is launched or during HMR.
63+
* May not run immediately if the app was started in background (e.g. push notification)
64+
* @param reason reason for bootstrap. @see {NgModuleReason}
65+
* @returns Promise to the bootstrapped NgModuleRef
66+
*/
67+
appModuleBootstrap: (reason: NgModuleReason) => Promise<NgModuleRef<T> | ApplicationRef>;
68+
/**
69+
* Loads a custom NgModule for the loading screen.
70+
* This loads only if appModuleBootstrap doesn't resolve synchronously (e.g. async APP_INITIALIZER).
71+
* @param reason reason for bootstrap. @see {NgModuleReason}
72+
* @returns Promise to the bootstrapped NgModuleRef. Must resolve immediately (no async initialization)
73+
*/
74+
loadingModule?: (reason: NgModuleReason) => Promise<NgModuleRef<K> | ApplicationRef>;
75+
/**
76+
* Simpler than loadingModule, this will show a view while the app is bootstrapping asynchronously.
77+
* @param reason reason for bootstrap. @see {NgModuleReason}
78+
* @returns View that will be shown while app boots
79+
*/
6280
launchView?: (reason: NgModuleReason) => AppLaunchView;
81+
/**
82+
* Wether we are running in an embedded context (e.g. embedding NativeScript in an existing app)
83+
*/
6384
embedded?: boolean;
6485
}
6586

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

74-
function emitModuleBootstrapEvent<T>(ref: NgModuleRef<T>, name: 'main' | 'loading', reason: NgModuleReason) {
95+
function emitModuleBootstrapEvent<T>(ref: NgModuleRef<T> | ApplicationRef, name: 'main' | 'loading', reason: NgModuleReason) {
7596
postAngularBootstrap$.next({
7697
moduleType: name,
7798
reference: ref,
7899
reason,
79100
});
80101
}
81102

82-
function destroyRef<T>(ref: NgModuleRef<T>, name: 'main' | 'loading', reason: NgModuleReason): void;
103+
function destroyRef<T>(ref: NgModuleRef<T> | ApplicationRef, name: 'main' | 'loading', reason: NgModuleReason): void;
83104
function destroyRef(ref: PlatformRef, reason: NgModuleReason): void;
84-
function destroyRef<T>(ref: PlatformRef | NgModuleRef<T>, name?: string, reason?: string): void {
105+
function destroyRef<T>(ref: PlatformRef | ApplicationRef | NgModuleRef<T>, name?: string, reason?: string): void {
85106
if (ref) {
86107
if (ref instanceof PlatformRef) {
87108
preAngularDisposal$.next({
@@ -90,7 +111,7 @@ function destroyRef<T>(ref: PlatformRef | NgModuleRef<T>, name?: string, reason?
90111
reason: name,
91112
});
92113
}
93-
if (ref instanceof NgModuleRef) {
114+
if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
94115
preAngularDisposal$.next({
95116
moduleType: name,
96117
reference: ref,
@@ -177,12 +198,29 @@ function runSynchronously(fn: () => void, done?: () => void): void {
177198
}
178199
}
179200

201+
function createProvidersConfig(options?: ApplicationConfig) {
202+
return {
203+
appProviders: [...NATIVESCRIPT_MODULE_PROVIDERS, ...NATIVESCRIPT_MODULE_STATIC_PROVIDERS, ...(options?.providers ?? [])],
204+
// platformProviders: INTERNAL_BROWSER_PLATFORM_PROVIDERS
205+
};
206+
}
207+
208+
export interface ApplicationConfig {
209+
/**
210+
* List of providers that should be available to the root component and all its children.
211+
*/
212+
providers: Array<Provider | EnvironmentProviders>;
213+
}
214+
export function bootstrapApplication(rootComponent: Type<unknown>, options?: ApplicationConfig): Promise<ApplicationRef> {
215+
return internalCreateApplication({ rootComponent, ...createProvidersConfig(options) });
216+
}
217+
180218
export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
181-
let mainModuleRef: NgModuleRef<T> = null;
182-
let loadingModuleRef: NgModuleRef<K>;
219+
let mainModuleRef: NgModuleRef<T> | ApplicationRef = null;
220+
let loadingModuleRef: NgModuleRef<K> | ApplicationRef;
183221
let platformRef: PlatformRef = null;
184222
let bootstrapId = -1;
185-
const updatePlatformRef = (moduleRef: NgModuleRef<T | K>, reason: NgModuleReason) => {
223+
const updatePlatformRef = (moduleRef: NgModuleRef<T | K> | ApplicationRef, reason: NgModuleReason) => {
186224
const newPlatformRef = moduleRef.injector.get(PlatformRef);
187225
if (newPlatformRef === platformRef) {
188226
return;
@@ -193,12 +231,12 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
193231
};
194232
let launchEventDone = true;
195233
let targetRootView: View = null;
196-
const setRootView = (ref: NgModuleRef<T | K> | View) => {
234+
const setRootView = (ref: NgModuleRef<T | K> | ApplicationRef | View) => {
197235
if (bootstrapId === -1) {
198236
// treat edge cases
199237
return;
200238
}
201-
if (ref instanceof NgModuleRef) {
239+
if (ref instanceof NgModuleRef || ref instanceof ApplicationRef) {
202240
if (ref.injector.get(DISABLE_ROOT_VIEW_HANDLING, false)) {
203241
return;
204242
}
@@ -262,10 +300,11 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
262300
return;
263301
}
264302
mainModuleRef = ref;
265-
ref.onDestroy(() => (mainModuleRef = mainModuleRef === ref ? null : mainModuleRef));
303+
304+
(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(() => (mainModuleRef = mainModuleRef === ref ? null : mainModuleRef));
266305
updatePlatformRef(ref, reason);
267306
const styleTag = ref.injector.get(NATIVESCRIPT_ROOT_MODULE_ID);
268-
ref.onDestroy(() => {
307+
(ref instanceof ApplicationRef ? ref.components[0] : ref).onDestroy(() => {
269308
removeTaggedAdditionalCSS(styleTag);
270309
});
271310
bootstrapped = true;
@@ -296,10 +335,10 @@ export function runNativeScriptAngularApp<T, K>(options: AppRunOptions<T, K>) {
296335
return;
297336
}
298337
loadingModuleRef = loadingRef;
299-
loadingModuleRef.onDestroy(() => (loadingModuleRef = loadingModuleRef === loadingRef ? null : loadingModuleRef));
338+
(loadingModuleRef instanceof ApplicationRef ? loadingModuleRef.components[0] : loadingModuleRef).onDestroy(() => (loadingModuleRef = loadingModuleRef === loadingRef ? null : loadingModuleRef));
300339
updatePlatformRef(loadingRef, reason);
301340
const styleTag = loadingModuleRef.injector.get(NATIVESCRIPT_ROOT_MODULE_ID);
302-
loadingRef.onDestroy(() => {
341+
(loadingModuleRef instanceof ApplicationRef ? loadingModuleRef.components[0] : loadingModuleRef).onDestroy(() => {
303342
removeTaggedAdditionalCSS(styleTag);
304343
});
305344
setRootView(loadingRef);

packages/angular/src/lib/cdk/list-view/list-view.component.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
1-
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';
1+
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';
22
import { ItemEventData, KeyedTemplate, LayoutBase, ListView, ObservableArray, profile, View } from '@nativescript/core';
33

44
import { extractSingleViewRecursive } from '../../element-registry/registry';
55
import { NativeScriptDebug } from '../../trace';
66
import { NgViewTemplate } from '../../view-refs';
7+
import { isListLikeIterable } from '../../utils/general';
78

89
const NG_VIEW = '_ngViewRef';
910

packages/angular/src/lib/public_api.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export * from './nativescript-common.module';
2121
export * from './loading.service';
2222
export * from './detached-loader-utils';
2323
// export * from './router/router.module';
24-
export { AppLaunchView, AppRunOptions, NgModuleEvent, NgModuleReason, disableRootViewHanding, onAfterLivesync, onBeforeLivesync, postAngularBootstrap$, preAngularDisposal$, runNativeScriptAngularApp } from './application';
24+
export { AppLaunchView, AppRunOptions, NgModuleEvent, NgModuleReason, disableRootViewHanding, onAfterLivesync, onBeforeLivesync, postAngularBootstrap$, preAngularDisposal$, runNativeScriptAngularApp, ApplicationConfig, bootstrapApplication } from './application';
2525
export * from './element-registry';
2626
export * from './nativescript-xhr-factory';
2727
export { EmulatedRenderer, NativeScriptRendererFactory, COMPONENT_VARIABLE as ɵCOMPONENT_VARIABLE, CONTENT_ATTR as ɵCONTENT_ATTR, HOST_ATTR as ɵHOST_ATTR } from './nativescript-renderer';

packages/angular/src/lib/utils/general.ts

+11
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,14 @@ export function once(fn: Function) {
3232
export interface ComponentType<T> {
3333
new (...args: any[]): T;
3434
}
35+
36+
export function isListLikeIterable(obj: any): boolean {
37+
if (!isJsObject(obj)) return false;
38+
return Array.isArray(obj) ||
39+
(!(obj instanceof Map) && // JS Map are iterables but return entries as [k, v]
40+
Symbol.iterator in obj); // JS Iterable have a Symbol.iterator prop
41+
}
42+
43+
export function isJsObject(o: any): boolean {
44+
return o !== null && (typeof o === 'function' || typeof o === 'object');
45+
}

0 commit comments

Comments
 (0)