4

How to define default variables for angular schematics "properties" in schema.json?

I have tried to look into the angular source code itself but I couldn't figure it out. I have found out that if the keyword "$source" is being used, somehow the string in front of it will be resolved to some value.

For example "argv" and "projectName" are two variables that they will be resolved to actual values when they are written like this:

     "project": {
         "alias": "p",
         "type": "string",
         "description": "The name of the project.",
         "$default": {
             "$source": "projectName"
         }
     },

or

    "name": {
      "type": "string",
      "description": "The package name for the new schematic.",
      "$default": {
        "$source": "argv",
        "index": 0
      }
    },

So how can I define my own variable? Are they actually variables? What other variables are available?

I also found another example which is not working if I copy-paste it into my own project:

    "version": {
      "type": "string",
      "description": "The version of the Angular CLI to use.",
      "visible": false,
      "$default": {
        "$source": "ng-cli-version"
      }
    },

Here find the full code

From my observation, I expect this to be resolved to a value, but I get undefiend.

Thank you folks in advance!

Ali Niaki
  • 128
  • 2
  • 11
  • It's not an answer, only a coment: the best article about schematic that work (Angular 7) I've found is https://medium.com/rocket-fuel/angular-schematics-simple-schematic-76be2aa72850 – Eliseo Feb 05 '19 at 07:58
  • That's not a good reference for this question. The closest thing I've found is this article from [Hakernoon](https://hackernoon.com/%EF%B8%8F-the-7-pro-tips-to-get-productive-with-angular-cli-schematics-b59783704c54), which is still not the answer to my question. – Ali Niaki Feb 06 '19 at 03:26

5 Answers5

1

The variables you define will certainly depend on the behavior for your schematic. For example, I have a new application schematic with a custom variable to allow users to specify the type of authentication to scaffold into the application:

To invoke this schematic from the command-line, the user would input ng new my-application-schematic-with-sso --collection=@simple-schematic --authentication=SSO (the x-prompt provides a list of options to the user if the command line switch is not explicitly provided. See also Angular ng-new schema for style options.

"authentication": {
    "description": "The authentication strategy to use",
    "default": "None",
    "x-prompt": {
        "message": "Which authentication strategy would you like to use?",
        "type": "list",
        "items": [
            {
                "value": "None",
                "label": "None"
            },
            {
                "value": "SSO",
                "label": "SSO"
            }
        ]
    }
},

This schematic also makes use of the argv pattern to capture the name of the workspace:

"name": {
    "description": "The name of the workspace",
    "type": "string",
    "$default": {
        "$source": "argv",
        "index": 0
    }
},...

So the command line syntax to invoke this schematic would look something like ng new my-new-schematic --collection=@simple-schematic and would generate a new workspace titled my-new-schematic. Since the --authentication flag is not provided, the user would be prompted to select either None or SSO.

One important behavior related to the schema and variables you define that can lead to the undefined issue (esp. when your variable has a default value) you noted in your original question is not correctly linking the schema to the schematic command.

For example, the schema property of the ng-new schematic object, indicates how to translate the provided CLI inputs into the corresponding object passed to your custom schematic.

{
  "$schema": "../node_modules/@angular-devkit/schematics/collection-schema.json",
  "extends": "@schematics/angular",
  "schematics": {
    "ng-new": {
      "description": "new workspace",
      "factory": "./ng-new",
      "schema": "./ng-new/schema.json"
    }
  }
}

Two different screenshots from debugging the tests depending on whether the schema property is specified or not:

No schema property fails to pick up default values specified

And again with the schema explicitly specified: Schema property picks up default values

ericksoen
  • 821
  • 8
  • 14
  • Please read my question again! This is completely unrelated to my question! I'm asking about **filing out the default values dynamically by the framework**, not "How to give users options". – Ali Niaki Feb 19 '19 at 15:47
  • I'd love to take another look. I interpreted "**So how can I define my own variable? Are they actually variables? What other variables are available?**" as your question. Can you restate the problem you are trying to solve, i.e., "I would like to add a default value to a property in a custom schematic?" (I think I can help out and I'll amend my answer once I have that extra bit of detail from you). – ericksoen Feb 19 '19 at 17:12
  • Sure, that's great! I need the Angular CLI to populate the default value of the parameters of the running schematics command.For example, "argv" keyword is something that CLI understands and replaces it (or passes) the right value at the run-time. Or another example, as I described in the question is the {"$source": "projectName"} which would be replaced with the actual project name. What I need is to be able to tell Angular to pass a value to the command by itself to the user will not be required to write it every time. – Ali Niaki Feb 19 '19 at 18:22
  • For example, let's say I want to get the installed angular-core version and pass it to the schematics via a parameter. – Ali Niaki Feb 19 '19 at 18:25
0

Just use "default": "YOUR_VALUE" but don't forget to run npm run build after.

laprof
  • 1,246
  • 3
  • 14
  • 27
  • That's the basic use case, the question is how to make it dynamic, not "how to provide a default value?". How to make "YOUR_VALUE" pragmatically changeable? – Ali Niaki Jun 25 '19 at 16:53
0

OffTopic: There is also interpolation .
You can find it in packages/_/devkit/package/schema.json. ;-)


You may have a look in
packages/angular_devkit/core/src/json/schema/registry.ts
where you can find a method called
addSmartDefaultProvider<T>(source: string, provider: SmartDefaultProvider<T>)

It looks like they are using this method to create such "smart defaults" as how they call it, but not always.
The check for argv in packages/angular/cli/utilities/json-schema.ts is done via
const $defaultIndex = (json.isJsonObject($default) && $default['$source'] == 'argv')
So no unified approach. Quite hacky imho.

Within the addSmartDefaultProvider method body you will find the following funny block:

// We cheat, heavily.
compilationSchemInfo.smartDefaultRecord.set(
// tslint:disable-next-line:no-any
   JSON.stringify((it as any).dataPathArr.slice(1, (it as any).dataLevel + 1) as string[]),
   schema,
);

If you are going to step further, you will discover:

Flash
  • 958
  • 1
  • 7
  • 9
0

DevRok was almost there. I think to fully answer you question we need to look at usage of addSmartDefaultProvider. I found these references in the 7.3.x branch (because that's what we are still using at work).

Reference 1 in packages/angular/cli/models/schematic-command.ts

workflow.registry.addSmartDefaultProvider('projectName', () => {

Reference 2 in packages/angular/cli/commands/new-impl.ts

this._workflow.registry.addSmartDefaultProvider('ng-cli-version', () => version);

So it seems ng-cli-version would only work when running ng new, not ng generate. It also seems that ng-cli-version and projectName are the only defined smart default providers for now, but it should be fairly straight forward to add more in the future.

For completion sake, I'd like to reference DevRok's answer for the pseudo smart default provider of argv being defined in packages/angular/cli/utilities/json-schema.ts.

0

Not sure how you set it in the schema.json... Not sure if there is a key value pair for it ("default":"value" didn't do anything).

So I worked around with an if statement in the factory function (index.ts)

if (!_options.yourVariable) {
     _options.yourVariable = 'yourDefaultValue'
}

Works well enough.

dippas
  • 58,591
  • 15
  • 114
  • 126
Joe Code
  • 31
  • 2