winkit-cli-angular v1.0.6
Winkit Angular
IMPORTANT: Before starting a Winkit Angular project, make sure to read about the basics of Winkit CLI.
Contents
- Adding Winkit Angular plugin
- Getting help
- Quick start
- Server configuration
- Primary database key
- Winkit Angular commands
- Known issues
- What's next?
Adding Winkit Angular plugin
To add the Winkit Angular plugin to your project, run:
winkit add:plugin angularGetting help
To get help on Winkit Angular commands run winkit angular --help
Quick start
- Configure your server
- Run
winkit angular init <projectName> - Choose the server you want to work with (Firestore or Strapi / Http)
- Provide the primary key you want to use in the project (or skip this step to use the default key)
- Enjoy!
Server configuration
Out-of-the-box Winkit Angular supports two server platforms - Strapi (visit website) and Firestore (visit website). Learn how to prepare your server for work with Winkit Angular:
Firestore
If you want to use Winkit with Firestore you must first configure your project in Firebase.
Once the project is created, open /src/environments/environment.ts and update firebaseConfig with the project info.
Do the same for /src/environments/environment.prod.ts with info for production environment.
Strapi
install strapi globally
npm install strapi@alpha -gRun the following command line in your terminal:
strapi new strapi-winkitGo to your project and launch the server:
cd strapi-winkit strapi startCreate your first admin user
Open
strapi-winkit/plugins/users-permissions/models/User.settings.jsonReplace the content with the following:
{ "connection": "default", "info": { "name": "user", "description": "" }, "attributes": { "username": { "type": "string", "minLength": 3, "unique": true, "configurable": false, "required": true }, "email": { "type": "email", "minLength": 6, "configurable": false, "required": true }, "password": { "type": "password", "minLength": 6, "configurable": false, "private": true }, "confirmed": { "type": "boolean", "default": false, "configurable": false }, "blocked": { "type": "boolean", "default": false, "configurable": false }, "role": { "model": "role", "via": "users", "plugin": "users-permissions", "configurable": false }, "userRole": { "default": "", "type": "string" }, "firstName": { "default": "", "type": "string" }, "lastName": { "default": "", "type": "string" }, "description": { "default": "", "type": "string" }, "telephone": { "default": "", "type": "string" }, "profileImg": { "default": "", "type": "string" }, "dateOfBirth": { "default": "", "type": "integer" }, "registeredAt": { "default": "", "type": "integer" }, "isMale": { "default": false, "type": "boolean" }, "media": { "collection": "file", "via": "related", "plugin": "upload" } }, "collectionName": "users-permissions_user" }Go to
http://localhost:1337/admin/plugins/content-manager/user?source=users-permissionsOpen the admin detail and populate the userRole field with the value
ADMINthen save.Now you can log into Winkit using these user credentials!
Primary database key
In your Winkit project you can either use the default primary database key or configure a custom primary database key.
The default key is 'id' (for the front-end) and '_id' (for the back-end). To use a different key provide it when prompted for the primary key upon initializing your project.
After initializing the project you can still change the primary key at any point in time by following these steps:
1. In the \<project folder>/winkit.conf.json file add or edit the primaryKey key by providing your value (minimum 2 characters).
2. Edit the \<project folder>/src/app/@core/models/Mappable.ts file by providing the name of your primaryKey as the first key of the Mappable interface;
3. Update all models in your project using the winkit angular update model ... command (more info);
Commands
winkit angular init|i \<projectName>
Initializes a new WDK Angular application in a new folder.
winkit angular init myprojectThe application works right out of the box. Included are: authentication, password recovery, user CRUD, profile page, file upload, etc.
winkit angular generate|g model \<name>
Generate a new model and its associated server model, ready to be mapped.
NOTE: If you are using Strapi remember to also:
- create the model on the server-side (ex. using the Strapi dashboard)
- add the
wid: stringparameter for both models
(if you are using Firestore you don't have to do anything because everything will be managed by WDK Angular)
Let's explain with an example:
winkit angular generate model FooThis command will generate the following file structure in the src/app/modules/ folder:
foo/models/Foo.tsServerFoo.tsFooDataFactory.ts
foo.conf.jsonfoo.module.tsfoo.routing.ts
NOTE: The foo/ directory and the foo.conf.json, foo.module.ts and foo.routing.ts files are only generated if they don't exist yet.
1. src/app/modules/foo/models/Foo.ts
map(obj: ServerFoo): Foo : maps the model starting from its server model.
Ex. const foo = new Foo().map(serverFoo);mapReverse(): ServerFoo : maps the model starting from its server model.
Ex. const serverFoo = foo.mapReverse();
2. src/app/modules/foo/models/ServerFoo.ts
static map(obj: Foo): ServerFoo : this method is called by the model mapReverse function.
It returns a server model (in this case
ServerFoo) with properties initialized using the logic provided in the privategetMappedAttributemethod (see below).Model properties which exist on the model only and don't exist on the server should have the
existsOnModelOnlyattribute in the \<model>.conf.json file set totrue. This way they will not be initialized on the server model returned by the method.static mapReverse(serverObject: ServerUser): User : this method is called by the model map function.
It returns a model (in this case
Foo) with properties initialized using the logic provided in the privategetReverseMappedAttributemethod (see below).Model properties which exist on the model only and don't exist on the server should have the
existsOnServerOnlyattribute in the \<model>.conf.json file set totrue. This way they will not be initialized on the model returned by the method.private static getMappedAttribute(model: User, prop: ModelProperty): string : this method is used in the server model map function to compute the value of the server model attribute.
The default logic is:
typeof model[localName] !== 'undefined' ? model[localName] : defaultValueTo provide your own logic, use the
caseclause which corresponds to the attribute.private static getReverseMappedAttribute(serverObject: ServerUser, prop: ModelProperty): string : this method is called by the server model mapReverse function to compute the value of the model attribute.
The default logic is:
typeof serverObject[serverName] !== 'undefined' ? serverObject[serverName] : defaultValueTo provide your own logic, use the
caseclause which corresponds to the attribute.
3. src/app/modules/foo/models/FooDataFactory.ts
File providing data used by the model's components. This file is updated by Winkit Angular based on the htmlConfig configuration in model properties.
4. src/app/modules/foo/foo.conf.json
The model configuration file. For more information on using the file, see the update model documentation.
5. src/app/modules/foo/foo.module.ts
The model module file - a standard Angular module which handles all the data and dependencies related to the model.
6. src/app/modules/foo/foo.routing.ts
The model routing file. It exports an object with 2 properties:
componentRoutes(required): an array of Angular type RoutesrouteInfo(optional): an object of type RouteInfo
winkit angular generate|g service \<modelName>
Generate a new service for given model, so you'll be ready to implement CRUD that works with the server chosen in the initialization.
NOTE: If the associated model does not exist yet, it is generated together with all necessary files (for more info see generate model section), before the the service file is generated.
Let's explain with an example:
winkit angular generate|g service FooThis command will generate the service and add it to the foo.module.ts:
src/app/modules/foo/service/foo.service.ts
on creation the service includes methods that allow you to:
- Create model
- Update model
- Delete model
- Get model by id
- Get paginated list
winkit angular generate|g detail \<modelName>
Generate a new detail component for given model and implement its routing, so you'll be ready to display model info.
Let's explain with an example:
winkit angular generate|g detail FooThis command will generate:
1. src/app/modules/foo/foo-detail/
- foo-detail.component.ts
- foo-detail.component.html
- foo-detail.component.scss
This component implements the CRUD of the Model, including validation.
2. foo/:id route in src/app/modules/foo/foo.routing.ts
By default the generated route is accessible just by authenticated user with ADMIN role. Thanks to this route you can:
- Create new instance of this model:
localhost:5000/foo/new- Detail / Update the instance with given id, ex. #1:
localhost:5000/foo/1
NOTE: The model info will be displayed in the detail component inside a <form> element. You can manage the form control elements inside the form by:
- editing the
<modelName>-detail.component.htmlfile (make sure to add"skipUpdate": trueto the property's config in \<model>.conf.json) - editing the
<modelName>.conf.jsonconfiguration file and updating the model and detail (more info) - passing an array of type FormControlList as the 2nd argument in the
<modelName>DataFactory.getFormControls()call in<modelName>-detail.component.ts(make sure to add"skipUpdate": trueto the property's config in \<model>.conf.json). For example:
.../someModel-detail.component.ts
this.formControlList = ZdueDataFactory.getFormControls(this, [
{name: 'sometext', type: FormControlType.TEXT}
]);.../someModel.conf.json
"properties": [
{"name": "someText", "skipUpdate": true},
...
]winkit angular generate|g list \<modelName>
Generates a new list component for given model, implements its routing and adds the link to the sidebar, so the list is ready to be displayed, including pagination and filtering.
Let's explain with an example:
winkit angular generate|g list FooThis command will generate:
1. src/app/modules/foo/foo-list/
- foo-list.component.ts
- foo-list.component.html
- foo-list.component.scss
This component includes the paginated table list and the filter component.
2. foo-list route in src/app/modules/foo/foo.routing.ts
By default the generated route is accessible just by authenticated user with ADMIN role. Every element in the table includes a link to its detail page.
3. foo-list link in src/app/@core/sidebar/sidebar-routes.config.ts
By default the link is visible just to ADMIN users.
winkit angular update|u model \<name>
Updates a model based on the configuration in the \<name>.conf.json file.
The schema of the \<name>.conf.json configuration file is the following:
- "properties" (
Array<ModelProperty>): an array of ModelProperty objects.
IMPORTANT: To exclude a ModelProperty from being updated by Winkit (ex. because you want to something custom with it), set its skipUpdate property to true (see below for more info);
The structure of the ModelProperty object is the following:
- name (
string: required): the name of the model property; - serverName (
string: optional): name of the corresponding server model property, if different from ModelProperty.name; - isOptional (
boolean: optional): adds TypeScript's optional class marker to a property (more info); - type (
string: optional): a string containing a typescript type (more info); - serverType (
string: optional): name of the corresponding server model type, if different from ModelProperty.type; - value (
any: optional): the default value assigned to the model on initialization. Setting this key results in ignoring the type and optional keys; - isManuallyUpdated (
boolean: optional): setting this value totrueresults in the model property being skipped if it already exists and is initialized on the model, when the model is updated usingwinkit angular update|u .... It also results in the property not being added to the model detail. For info on rectifying this, see this section; - mapReverseName (
string: optional): The name of the model property to which the server model property value should be assigned in the mapReverse method of the server model; - relationship (
string: optional): Maps the value of the provided model property to the current property, e.g. the following configuration:{"name": "wid", "relationship": "id", ...}will result in mapping the value of theidproperty to thewidproperty in the model constructor and the server model map method; - mapReverseRelationship (
string: optional): Maps the value of the provided server model property to the current property, e.g. the following configuration:{"name": "wid", "mapReverseRelationship": "_id", ...}will result in mapping the value of the_idproperty to thewidproperty in the server model mapReverse method; - existsOnModelOnly (
boolean: optional): Excludes the model property from being initialized inside the Server\<Model>.map method; - existsOnServerOnly (
boolean: optional): Excludes the model property from being initialized inside the Server\<Model>.mapReverse method; - htmlConfig (
object: optional): An object containing configuration of a single form control element. Must be set for the form control element to be displayed in the detail component of a given model. For more information see Structure of the htmlConfig object section below;
NOTE: The primary key property settings are not affected by the \<name>.conf.json configuration.
STRUCTURE OF THE htmlConfig OBJECT
The structure of htmlConfig object mostly reflects attributes of an HTMLInputElement but also has some additional settings:
GENERAL
- type (
FormControlType | HTMLInputElement.type): a HTMLInputElement type or one of the special types listed in the FormControlType enum; - ngIf (
boolean: optional): sets the value of the angularngIfdirective (more info) of the Form Element; - required (
boolean: optional): sets therequiredattribute of the input element; - disabled (
boolean: optional): sets thedisabledattribute of the input element; - readonly (
boolean: optional): sets thereadonlyattribute of the input element; - pattern (
boolean: optional): sets thepatternattribute of the input element; - wrapperClass (
string: optional): The class of the generated HTML element. Default value:col-sm-6 mb-3 - innerWrapperClass (
string: optional): The class of the<div>element that wraps the contents of the generated HTML element. - order (
number | string: optional): Sets the CSS order property of the form element. - inputFeedbackText (
string: optional): The text inside the<small>element, which is displayed when the value of the input is invalid. NOTE: Setting theinputFeedbackTextattribute is the condition for displaying the<small>feedback element. - inputFeedbackExample (
string: optional): The italicized text displayed in round brackets afterinputFeedbackTextinside the<small>element.
FORM ELEMENT TYPE: SELECT
- options (
Array<string | {name: string, value: any}>: required): The list of<option>elements that will be generated inside the<select>element.
FORM ELEMENT TYPE: MEDIA
- allowedTypes (
Array<MediaType>: required): Array of MediaType enum values.
FORM ELEMENT TYPE: TEXTAREA
- rows (
number: optional): sets therowsattribute of the<textarea>element. Default value:6
IMPORTANT:
- Inside htmlConfig you can use the
thatkeyword to reference an external object. By default this object is the current model's detail component class (ex. FooDetailComponent). This is achieved by passingthisas the 1st argument in the<modelName>DataFactory.getFormControls()call in<modelName>-detail.component.ts. To reference a different object pass it as the 1st argument instead ofthis. - To set a string literal as a value in htmlConfig wrap it in additional quotes, ex.:
"'example string literal'"
Let's explain with an example:
src/app/modules/foo/foo.conf.json
{
"properties": [
{"name": "first", "optional": true},
{"name": "second", "type": "{id: number, [key: string]: any, someKey: SomeClass[]}"},
{"name": "third", "value": "['some string', 1.2]"},
{"name": "fourth", "skipUpdate": true},
{"name": "fifth", "type": "string", "htmlConfig": {
"type": "FormControlType.SELECT", "required": true, "options": "that.fifthPropOptions"
}}
]
}With the above configuration, running this command:
winkit angular update|u model Foowill result in the following property settings in Foo.ts and ServerFoo.ts models:
1. src/app/modules/foo/models/Foo.ts
...
id: string;
wid: string;
first?;
second: {id: number, [key: string]: any, someKey: SomeClass[]};
third = ['some string', 1.2];
fifth: string;
...
constructor()
constructor(id?: string,
first?,
second?: {id: number, [key: string]: any, someKey: SomeClass[]},
third?: any,
fifth?: string) {
this.id = typeof id !== 'undefined' ? id : null;
this.wid = typeof id !== 'undefined' ? id : null;
this.first = typeof first !== 'undefined' ? first : null;
this.second = typeof second !== 'undefined' ? second : null;
this.third = typeof third !== 'undefined' ? third : ['some string', 1.2];
this.fifth = typeof fifth !== 'undefined' ? fifth : null;
}
...2. src/app/modules/foo/models/ServerFoo.ts
...
_id?: string;
wid?: string;
first?;
second: {id: number, [key: string]: any, someKey: SomeClass[]};
third: any = ['some string', 1.2];
fifth: string;
...
static map(obj: Zuno): ServerZuno {
const o = {} as ServerZuno;
o._id = typeof obj.id !== 'undefined' ? obj.id : null;
o.wid = typeof obj.id !== 'undefined' ? obj.id : null;
o.first = typeof obj.first !== 'undefined' ? obj.first : null;
o.second = typeof obj.second !== 'undefined' ? obj.second : null;
o.third = typeof obj.third !== 'undefined' ? obj.third : null;
o.fifth = typeof obj.fifth !== 'undefined' ? obj.fifth : null;
return o;
}
...
static mapReverse(serverObject: ServerZuno): Zuno {
const o = {} as Zuno;
o.id = typeof serverObject._id !== 'undefined' ? serverObject._id : null;
o.wid = typeof serverObject._id !== 'undefined' ? serverObject._id : null;
o.first = typeof serverObject.first !== 'undefined' ? serverObject.first : null;
o.second = typeof serverObject.second !== 'undefined' ? serverObject.second : null;
o.third = typeof serverObject.third !== 'undefined' ? serverObject.third : null;
o.fifth = typeof serverObject.fifth !== 'undefined' ? serverObject.fifth : null;
return o;
}
...3. src/app/modules/foo/models/FooDataFactory.ts
...
static getFormControls = (that: any, customControlList: FormControlList = []): FormControlList => {
const generatedFormControls: FormControlList = [
{name: 'fifth', required: true, type: FormControlType.SELECT, options: that.fifthPropOptions}
];
...Known issues
- Strapi has been reported to not support Node.js >9 versions on certain versions of Windows. Link to issue
- In Strapi 3.0.0-alpha.15 and 3.0.0-alpha.16 there is a bug causing update actions of User objects to fail. For possible solutions see this Strapi issue report
- In Strapi 3.0.0-alpha.15 and 3.0.0-alpha.16 there is a bug causing create requests for non-User models to return a 404 error, even though instances of models are correctly created. See this Strapi issue report
- In Firestore the filter feature is case sensitive and must match the whole value
What's next?
winkit angular update|u model \<modelName>
- Add the generated parameter to the table header in the list component
winkit angular delete|d \<elementType> \<modelName>
This command will delete the model and all its associated files.