2

When using Schematics to generate components in Angular, it will attempt to import the newly generated component into the NgModule, this is mandatory in order for the component to work and saves time.

However, when creating my own schematics, I am unable to find a correct way to do the same and import my library into the app.component.ts's NgModule. When using ng-add schematics, how can you have the schematic automatically add its own entry to the NgModule?

The schematic needs to do two things:

  1. Import the library
  2. Add it to the NgModule imports

Before:

@NgModule({
  declarations: [],
  imports: [],
  exports: []
})
export class appModule { }

After:

import {library} from 'library';

@NgModule({
  declarations: [],
  imports: [library],
  exports: []
})
export class appModule { }

I know a function to do this exists in the angular devkit because the "official" schematics for components and such use it. How would I do the same?

anderio Moga
  • 425
  • 7
  • 11

2 Answers2

5

For the first case, something like the code below will help you:

export function addImportStatement(tree: Tree, filePath: string, type: string, file: string): void {
    let source = getTsSourceFile(tree, filePath);
    const importChange = insertImport(source, filePath, type, file) as InsertChange;
    if (!(importChange instanceof NoopChange)) {
        const recorder = tree.beginUpdate(filePath);
        recorder.insertLeft(importChange.pos, importChange.toAdd);
        tree.commitUpdate(recorder);
    }
}

For the second one, I don't remember at the momment but you can check angular-cli schematics repository in order to see how it is done for things like modules.

Cheers!

KingDarBoja
  • 1,033
  • 6
  • 12
2

Here is a function that uses the 'addImportToModule' function from `@schematics/angular/utility/ast-utils' utils..

function _addImport(
  importPath: string,
  importName: string,
  _options: Partial<Schema>,
  tree: Tree
): Tree {
  const appModulePath = `/${_options.projectName}/src/app/app.module.ts`;
  const appModuleFile = tree.read(normalize(appModulePath)).toString('utf-8');
  if (!appModuleFile) {
    throw new SchematicsException('app.module.ts not found');
  }
  const result = new AddToModuleContext();
  result.source = ts.createSourceFile(
    appModulePath,
    appModuleFile,
    ts.ScriptTarget.Latest,
    true
  );
  result.relativePath = importPath;
  result.classifiedName = importName;
  const importsChanges = addImportToModule(
    result.source,
    appModulePath,
    result.classifiedName,
    result.relativePath
  );
  const importRecorder = tree.beginUpdate(appModulePath);
  for (const change of importsChanges) {
    if (change instanceof InsertChange) {
      importRecorder.insertLeft(change.pos, change.toAdd);
    }
  }
  tree.commitUpdate(importRecorder);
  return tree;
}

At the end of one of my other functions that return a Rule, inside my call to chain([])....., I called this function like so (I needed to add the HttpClientModule to imports, and if the user installed flex layout during the options x-prompts, I needed to add the FlexLayoutModule to imports as well. There are multiple other utility functions such as addDependencyToModule, addExportToModule, etc, that can be used to tweak any of your module.ts files

the call:

tree = _addImport(
      '@angular/common/http',
      'HttpClientModule',
      _options,
      tree
    );
    if (_options.dependencies.includes('flexLayout')) {
      tree = _addImport(
        '@angular/flex-layout',
        'FlexLayoutModule',
        _options,
        tree
      );
    }
    return tree;

This successfully added both modules to my imports: [] in app.module, as well as the import statements up top. There is also a utility function 'buildRelativePath' from '@schematics/angular/utility/find-module, which can help if you need to build relative paths from wherever you module you are working with is located.

Dharman
  • 30,962
  • 25
  • 85
  • 135
Chris Wilson
  • 135
  • 3
  • 16