npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

ngx-struct

v0.0.5

Published

A CLI tool for quickly scaffolding Angular project structures, including components, services, and stores.

Downloads

289

Readme

Ngx Struct

Ngx Struc is a command-line interface tool designed to streamline the process of setting up new feature structures within Angular projects. By automating the creation of commonly used directory structures and files, this tool significantly reduces the time and effort involved in the initial setup of features, services, and stores in Angular applications.

Features

  • Interactive Scaffold Creation: Promptly generates a customizable structure for your Angular feature, including essential directories and files.
  • Configurable Components: Based on user input, it creates a feature component with the option of marking it as standalone or nested within another feature.
  • Service and Store Generation: Automatically generates service and store files within the data directory, pre-populated with template code for state management and data handling, leveraging RxJS for reactive data services.
  • Easy Integration: Designed to be seamlessly integrated into existing Angular projects, enhancing development workflows without disrupting the current architecture.

How It Works

Upon execution, Ngx Struct prompts the user for the name of the new feature. It then creates a dedicated folder for the feature, subdividing it into data, feature, and ui directories. The data directory gets populated with a service and a store file, each containing boilerplate code to kickstart development. Depending on user preference, it either creates a new Angular component within the feature directory or leaves it empty for nested features, streamlining the development process for both standalone and nested components.

Getting Started

This CLI tool is designed to be installed globally and used within any Angular project to facilitate rapid development setup and consistent code organization.

Installation

To install the Angular Scaffold CLI globally on your system, run:

npm install --dev ngx-struct

Usage

Navigate to your Angular project's root directory and run:

ngx-struct

Follow the interactive prompts to specify your feature's name and whether it's a standalone or nested feature. The CLI will handle the rest, creating the specified structure and files within your project.


Project Structure Overview

This script automates the setup of a new feature within an Angular application, organizing code by its responsibilities and creating a consistent directory structure. The structure can be either flat for single features or nested for features containing sub-features. Below is an explanation of the created directories and files.

Base Structure

For every new feature, the script generates the following base directories and files within the src/app directory:

  • data/: Contains services and stores necessary for the feature's data management.
    • *.service.ts: It is the file that houses all the logic belonging to this feature.
    • *.store.ts: A store file for managing the state of the feature on memory (local/session storage).
  • feature/ (optional): Created for features that are not nested. It includes the Angular component files:
    • *.component.ts: The typeScript (standalone angular component) file defining what we call Smart Components.
  • ui/: This folder contains what are known as dummy components, which are merely presentation components.

Nested Structure

For nested features, the script additionally creates a feature/ directory within the specified parent feature directory. Inside this feature/ directory, a similar structure to the base one is replicated for the nested feature:

  • data/: Contains services and stores for the nested feature.
  • feature/: Includes the Smart component files for the nested feature, mirroring the base structure.
  • ui/: Dummy components.

alt

Data Diagram

Within the feature.service.ts component, a crud-type structure is generated by default with the following key points:

  • sources$: Act as triggers for different actions.
  • reducers: Handle state update logic in response to actions triggered by sources$. Each subscription listens to its respective source$ and updates the private states as necessary.
  • state: Private states related to the feature.
  • selectors: They offer a way to derive data from the private states, ensuring the UI components get the latest updated values.

alt

The idea behind the connection between rxjs and signals is to exploit the potential of both tools.

alt

The final result should be something like this:

alt

Example Structure

Assuming we create a feature named dashboard without nesting:

src/app/dashboard/
├── data/
│   ├── dashboard.service.ts
│   └── dashboard.store.ts
├── ui/
└── feature/
    ├── dashboard.component.ts
    ├── dashboard.component.html
    └── dashboard.component.scss

For a nested feature scenario, where dashboard contains a nested feature named reports:

src/app/dashboard/
├── data/
│   ├── dashboard.service.ts
│   └── dashboard.store.ts
├── ui/
└── feature/
    ├── dashboard.component.ts
    ├── dashboard.component.html
    ├── dashboard.component.scss
    └── reports/
        ├── data/
        │   ├── reports.service.ts
        │   └── reports.store.ts
        ├── ui/
        └── feature/
            ├── reports.component.ts
            ├── reports.component.html
            └── reports.component.scss

Data layer example *.service.ts & *.store.ts

@Injectable({
  providedIn: 'root',
})
export class SomeService {
  private readonly someApiService: SomeApiService = inject(SomeApiService);
  private readonly someStore: SomeStore = inject(SomeStore);

  //state
  private $someState = signal<T>({
    data: [],
    status: 'loading',
    error: null,
  });

  //selectors
  $data = computed(() => this.$someState().data);
  $status = computed(() => this.$someState().status);

  //sources
  retry$ = new Subject<void>();
  private someDataLoaded$ = this.retry$.pipe(
    startWith(null),
    switchMap(() =>
      this.someApiService.getSomeData().pipe(
        retry({
          delay: error => {
            this.$someState.update(state => ({ ...state, error, status: 'error' }));
            return this.retry$;
          },
        }),
      ),
    ),
  );
  otherAction$ = new Subject<any>();

  constructor() {
    //reducers
    this.someDataLoaded$.pipe(takeUntilDestroyed()).subscribe({
      next: response => {
        this.$someState.update(state => ({
          ...state,
          data: response.data,
          status: 'success',
        }));
      },
      error: error => this.$someState.update(state => ({ ...state, error, status: 'error' })),
    });

    this.otherAction$.pipe(takeUntilDestroyed()).subscribe((subjectReceived: any) => {
      if (subjectReceived) {
        // do something
      } else {
        // do something
      }
    });

    this.retry$
      .pipe(takeUntilDestroyed())
      .subscribe(() => this.$someState.update(state => ({ ...state, status: 'loading' })));

    effect(() => {
      if (this.$someState().status === 'success') {
        this.someStore.saveLocalData(this.$data());
      }
    });
  }
}
export const LOCAL_STORAGE = new InjectionToken<Storage>('window local storage object', {
  providedIn: 'root',
  factory: () => {
    return inject(PLATFORM_ID) === 'browser' ? window.localStorage : ({} as Storage);
  },
});

@Injectable({
  providedIn: 'root',
})
export class SomeStore {
  storage = inject(LOCAL_STORAGE);

  loadSomeLocalData(): Observable<any[]> {
    const data = this.storage.getItem('data');
    return of(data ? (JSON.parse(data) as data[]) : []);
  }

  saveLocalData(data: data[]): void {
    this.storage.setItem('data', JSON.stringify(data));
  }
}

With this approach we make sure that all the logic is in the same file and we expose actions that allow us to modify the state indirectly, also exposing only the necessary information and not the entire state.

The components (smart ones) only see the sources (actions) and the selectors (partial state)

Usage

  • Creating a Feature: Run the script and follow the prompts to specify the feature name. If the feature is not nested, the script creates the base structure.
  • Creating a Nested Feature: When prompted, indicate that the component is nested and provide the name of the child component. The script then generates both the parent and nested feature structures.

This structure emphasizes separation of concerns by dividing the feature into logical sections, making it scalable and maintainable. It supports both simple and complex feature developments, accommodating various project sizes and complexities.


TODO

  • [ ] Commands to generate services
  • [ ] Commands to generate stores
  • [ ] Generate folder /shared/... with injection tokens to avoid innecesary code
  • [ ] Command to generate a whole crud feature