-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(qwik-nx): preliminary implementation of angular integration
- Loading branch information
1 parent
71e06dc
commit 1b55fdd
Showing
18 changed files
with
797 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
16 changes: 16 additions & 0 deletions
16
packages/qwik-nx/src/generators/integrations/angular-in-app/files/demo/index.tsx.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { component$ } from '@builder.io/qwik'; | ||
import type { DocumentHead } from '@builder.io/qwik-city'; | ||
import { AngularCounterComponent } from '../../integrations/angular'; | ||
|
||
export default component$(() => { | ||
return ( | ||
<> | ||
<h1>Qwik/Angular demo</h1> | ||
<AngularCounterComponent initialCountValue={2} /> | ||
</> | ||
); | ||
}); | ||
|
||
export const head: DocumentHead = { | ||
title: 'Qwik Angular', | ||
}; |
113 changes: 113 additions & 0 deletions
113
...ages/qwik-nx/src/generators/integrations/angular-in-app/files/material/index.tsx.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,113 @@ | ||
import { component$, useSignal } from '@builder.io/qwik'; | ||
import type { DocumentHead } from '@builder.io/qwik-city'; | ||
import { | ||
MaterialSlider, | ||
MaterialButton, | ||
type ButtonComponentProps, | ||
MaterialTable, | ||
type TableUserData, | ||
} from '../../integrations/angular'; | ||
|
||
export default component$(() => { | ||
const show = useSignal(false); | ||
const count = useSignal(0); | ||
const btnColor = useSignal<ButtonComponentProps['color']>('primary'); | ||
const users = useSignal(Array.from({ length: 100 }, (_, k) => createNewUser(k + 1))); | ||
|
||
return ( | ||
<div> | ||
<h1> | ||
Welcome to Qwik Angular<span class="lightning">⚡️</span> | ||
</h1> | ||
|
||
<div style="width: 80%; margin: 2rem auto"> | ||
<select | ||
value={btnColor.value} | ||
onChange$={(ev) => { | ||
btnColor.value = (ev.target as any).value; | ||
}} | ||
> | ||
<option>warn</option> | ||
<option>accent</option> | ||
<option selected>primary</option> | ||
</select> | ||
|
||
<MaterialSlider | ||
client:visible | ||
sliderValue={count.value} | ||
sliderValueChanged$={(value: number) => { | ||
count.value = value; | ||
}} | ||
/> | ||
|
||
<MaterialButton color={btnColor.value} host:onClick$={() => alert('click')}> | ||
Slider is {count.value} | ||
</MaterialButton> | ||
|
||
<MaterialButton | ||
color="accent" | ||
client:hover | ||
host:onClick$={() => { | ||
show.value = true; | ||
}} | ||
> | ||
Show table | ||
</MaterialButton> | ||
|
||
{show.value && <MaterialTable client:only users={users.value}></MaterialTable>} | ||
</div> | ||
</div> | ||
); | ||
}); | ||
|
||
export const head: DocumentHead = { | ||
title: 'Qwik Angular', | ||
}; | ||
|
||
/** Builds and returns a new User. */ | ||
function createNewUser(id: number): TableUserData { | ||
/** Constants used to fill up our data base. */ | ||
const FRUITS: string[] = [ | ||
'blueberry', | ||
'lychee', | ||
'kiwi', | ||
'mango', | ||
'peach', | ||
'lime', | ||
'pomegranate', | ||
'pineapple', | ||
]; | ||
const NAMES: string[] = [ | ||
'Maia', | ||
'Asher', | ||
'Olivia', | ||
'Atticus', | ||
'Amelia', | ||
'Jack', | ||
'Charlotte', | ||
'Theodore', | ||
'Isla', | ||
'Oliver', | ||
'Isabella', | ||
'Jasper', | ||
'Cora', | ||
'Levi', | ||
'Violet', | ||
'Arthur', | ||
'Mia', | ||
'Thomas', | ||
'Elizabeth', | ||
]; | ||
const name = | ||
NAMES[Math.round(Math.random() * (NAMES.length - 1))] + | ||
' ' + | ||
NAMES[Math.round(Math.random() * (NAMES.length - 1))].charAt(0) + | ||
'.'; | ||
|
||
return { | ||
id: id.toString(), | ||
name: name, | ||
progress: Math.round(Math.random() * 100).toString(), | ||
fruit: FRUITS[Math.round(Math.random() * (FRUITS.length - 1))], | ||
}; | ||
} |
86 changes: 86 additions & 0 deletions
86
packages/qwik-nx/src/generators/integrations/angular-in-app/generator.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
import { | ||
formatFiles, | ||
generateFiles, | ||
joinPathFragments, | ||
ProjectType, | ||
readProjectConfiguration, | ||
Tree, | ||
} from '@nx/devkit'; | ||
import * as path from 'path'; | ||
import { AngularInAppGeneratorSchema } from './schema'; | ||
import { angularInit } from '../../../utils/angular/init'; | ||
|
||
interface NormalizedSchema extends AngularInAppGeneratorSchema { | ||
sourceRoot: string; | ||
projectRoot: string; | ||
projectType: ProjectType; | ||
} | ||
|
||
function normalizeOptions( | ||
tree: Tree, | ||
options: AngularInAppGeneratorSchema | ||
): NormalizedSchema { | ||
const projectConfig = readProjectConfiguration(tree, options.project); | ||
|
||
return { | ||
...options, | ||
installMaterialExample: options.installMaterialExample !== false, | ||
sourceRoot: projectConfig.sourceRoot ?? projectConfig.root + '/src', | ||
projectRoot: projectConfig.root, | ||
projectType: projectConfig.projectType!, | ||
}; | ||
} | ||
|
||
function addFiles(tree: Tree, normalizedOptions: NormalizedSchema): void { | ||
const filePath = normalizedOptions.installMaterialExample | ||
? 'material' | ||
: 'demo'; | ||
generateFiles( | ||
tree, | ||
path.join(__dirname, 'files', filePath), | ||
joinPathFragments(normalizedOptions.sourceRoot, 'routes/angular'), | ||
{} | ||
); | ||
} | ||
|
||
export async function angularInAppGenerator( | ||
tree: Tree, | ||
schema: AngularInAppGeneratorSchema | ||
) { | ||
const normalizedOptions = normalizeOptions(tree, schema); | ||
|
||
if (normalizedOptions.projectType !== 'application') { | ||
throw new Error( | ||
`Only applications are supported, "${normalizedOptions.project}" is a library.` | ||
); | ||
} | ||
|
||
const demoFilePath = joinPathFragments( | ||
normalizedOptions.sourceRoot, | ||
'integrations/angular' | ||
); | ||
|
||
if (tree.exists(demoFilePath)) { | ||
throw new Error( | ||
`Looks like angular integration has already been configured for ${normalizedOptions.project}. "${demoFilePath}" already exists.` | ||
); | ||
} | ||
|
||
const initCallback = angularInit(tree, { | ||
demoFilePath: joinPathFragments( | ||
normalizedOptions.sourceRoot, | ||
'integrations/angular' | ||
), | ||
installMaterialExample: !!normalizedOptions.installMaterialExample, | ||
projectRoot: normalizedOptions.projectRoot, | ||
isApp: true, | ||
}); | ||
addFiles(tree, normalizedOptions); | ||
if (!normalizedOptions.skipFormat) { | ||
await formatFiles(tree); | ||
} | ||
|
||
return initCallback; | ||
} | ||
|
||
export default angularInAppGenerator; |
5 changes: 5 additions & 0 deletions
5
packages/qwik-nx/src/generators/integrations/angular-in-app/schema.d.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
export interface AngularInAppGeneratorSchema { | ||
project: string; | ||
installMaterialExample?: boolean; | ||
skipFormat?: boolean; | ||
} |
31 changes: 31 additions & 0 deletions
31
packages/qwik-nx/src/generators/integrations/angular-in-app/schema.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
{ | ||
"$schema": "http://json-schema.org/schema", | ||
"$id": "AngularInApp", | ||
"title": "", | ||
"type": "object", | ||
"properties": { | ||
"project": { | ||
"type": "string", | ||
"description": "Name of the project to add Angular integration to", | ||
"$default": { | ||
"$source": "argv", | ||
"index": 0 | ||
}, | ||
"x-prompt": "Name of the project to add Angular integration to" | ||
}, | ||
"installMaterialExample": { | ||
"type": "boolean", | ||
"description": "Add dependencies for the Angular Material and qwikified example component, that uses it", | ||
"x-priority": "important", | ||
"default": true, | ||
"x-prompt": "Do you want to have Angular Material example installed?" | ||
}, | ||
"skipFormat": { | ||
"description": "Skip formatting files.", | ||
"type": "boolean", | ||
"x-priority": "internal", | ||
"default": false | ||
} | ||
}, | ||
"required": ["project"] | ||
} |
51 changes: 51 additions & 0 deletions
51
packages/qwik-nx/src/utils/angular/files/demo/components/counter.component.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import { Component, EventEmitter, Input, Output, type OnInit } from '@angular/core'; | ||
import type { QwikifiedComponentProps, WithRequiredProps } from 'qwik-angular'; | ||
|
||
type CounterComponentInputs = 'initialCountValue' | 'heading'; | ||
|
||
type CounterComponentOutputs = 'countChanged'; | ||
|
||
type RequiredPropValues = 'initialCountValue'; | ||
|
||
// using utility types to assemble a type object for qwikified CounterComponent | ||
// that has all inputs and typed output handlers of Angular CounterComponent | ||
type OptionalCounterComponentProps = QwikifiedComponentProps< | ||
CounterComponent, | ||
CounterComponentInputs, | ||
CounterComponentOutputs | ||
>; | ||
|
||
// also marking "initialCountValue" as required and exporting the final type | ||
export type CounterComponentProps = WithRequiredProps< | ||
OptionalCounterComponentProps, | ||
RequiredPropValues | ||
>; | ||
|
||
@Component({ | ||
selector: 'app-angular-counter', | ||
template: ` | ||
<div> | ||
<h1>{{ heading }}</h1> | ||
<p>{{ count }}</p> | ||
<button (click)="handleClick()">Increment</button> | ||
</div> | ||
`, | ||
}) | ||
export class CounterComponent implements OnInit { | ||
@Input() initialCountValue: number = 0; | ||
@Input() heading = 'Simple Angular Counter'; | ||
|
||
@Output() readonly countChanged = new EventEmitter<number>(); | ||
|
||
private count: number; | ||
|
||
ngOnInit(): void { | ||
this.count = this.initialCountValue; | ||
} | ||
|
||
handleClick(): void { | ||
this.count++; | ||
this.countChanged.emit(this.count); | ||
console.log(`Count: ${this.count}`); | ||
} | ||
} |
8 changes: 8 additions & 0 deletions
8
packages/qwik-nx/src/utils/angular/files/demo/index.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
import { qwikify$ } from 'qwik-angular'; | ||
import { type CounterComponentProps, CounterComponent } from './components/counter.component'; | ||
|
||
export const AngularCounterComponent = qwikify$<CounterComponentProps>(CounterComponent, { | ||
eagerness: 'hover', | ||
}); | ||
|
||
export { CounterComponentProps }; |
23 changes: 23 additions & 0 deletions
23
...rc/utils/angular/files/material/integration-files/components/button.component.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
import { Component, Input } from '@angular/core'; | ||
import { MatButtonModule } from '@angular/material/button'; | ||
import type { QwikifiedComponentProps } from 'qwik-angular'; | ||
|
||
type ButtonComponentInputProps = 'color'; | ||
|
||
export type ButtonComponentProps = QwikifiedComponentProps< | ||
ButtonComponent, | ||
ButtonComponentInputProps | ||
>; | ||
|
||
@Component({ | ||
imports: [MatButtonModule], | ||
standalone: true, | ||
template: ` | ||
<button mat-raised-button [color]="color"> | ||
<ng-content></ng-content> | ||
</button> | ||
`, | ||
}) | ||
export class ButtonComponent { | ||
@Input() color: 'primary' | 'accent' | 'warn' = 'primary'; | ||
} |
32 changes: 32 additions & 0 deletions
32
...src/utils/angular/files/material/integration-files/components/input.component.ts.template
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
import { Component } from '@angular/core'; | ||
import { FormsModule, ReactiveFormsModule } from '@angular/forms'; | ||
import { MatFormFieldModule } from '@angular/material/form-field'; | ||
import { MatInputModule } from '@angular/material/input'; | ||
import { MatIconModule } from '@angular/material/icon'; | ||
import { CommonModule } from '@angular/common'; | ||
|
||
@Component({ | ||
selector: 'input-clearable-example', | ||
template: ` | ||
<mat-form-field class="example-form-field"> | ||
<mat-label>Clearable input</mat-label> | ||
<input matInput type="text" [(ngModel)]="value" /> | ||
<button *ngIf="value" matSuffix mat-icon-button aria-label="Clear" (click)="value = ''"> | ||
<mat-icon>close</mat-icon> | ||
</button> | ||
</mat-form-field> | ||
`, | ||
standalone: true, | ||
providers: [], | ||
imports: [ | ||
MatFormFieldModule, | ||
MatInputModule, | ||
FormsModule, | ||
ReactiveFormsModule, | ||
MatIconModule, | ||
CommonModule, | ||
], | ||
}) | ||
export class InputComponent { | ||
value = 'Clear me'; | ||
} |
Oops, something went wrong.