Choosing NgXs over NgRx is a first step into a more expressive code. Below you have other tips on how to keep your codebase short and readable 👇

Protips 🤩

Start with the StateModel interface (we gonna use it below). It will make your code simpler and more error-proof.

export interface StateModel {
  zoo: ZooStateModel
}

@State<ZooStateModel>({
  name: 'zoo',
  defaults: []
})
@Injectable()
export class ZooState {}

Don't write @Selector() just to get a value from the store…

export class ZooState {
  @Selector()
  static loading({ loading }): boolean {
    return loading;
  }
}

@Component({ ... })
export class ZooComponent {
  @Select(State.loading) loading$: Observable<boolean>

  constructor(private store: Store) {}
}

... use @Select and arrow function instead (and our StateModel interface).

@Component({ ... })
export class ZooComponent {
  @Select((state: StateModel) => state.loading) loading$;

  constructor(private store: Store) {}
}

Write selectors only if you have some business logic inside:

@Selector()
static loading({ wait, array, name }): boolean {
  if (array.length === 0) return true;
  if (name === "") return true;
  return wait;
}

Use @Select only if you use property once in your template (with async pipe)

@Component({
  selector: 'zoo-component',
  template: `<loader *ngIf="loading$ | async"></loader>`,
})
export class ZooComponent {
  @Select((state: StateModel) => state.loading) loading$ Observable<boolean>;
}

If you use state property in multiple places across a template, use select() to get actual value. Don't add Observable as a property in your component, instead create a simple property (like boolean below), and write subscription logic (assigning) right after the select() method.

@Component({
  selector: 'zoo-component',
  template: `
    <loader *ngIf="loading">
    <div *ngIf="!loading">Hello</div>
    <button [disabled]="loading">submit</button>  
  `,
})
export class ZooComponent implements OnInit {
  loading: boolean;

  ngOnInit(): void {
    this.store
      .select((state: StateModel) => state.loading)
      .subscribe(loading => this.loading = loading);
  }
}

Last thing: if you separate business logic from NgXs (eg. by putting it in helper class with static methods), you will achieve great readability and testability of your code. Seriously I would risk a statement, that if you use Angular and NgXs with some dose of discipline (like the rules above), you can write unit tests only for pure functions in your helpers, without any dependencies (like DOM, NgXS etc.).

export class ZooHelper {
  static countAnimals(animals: Animal[]): number {
    return animals.reduce((count, animal) => {
      if (animal.isAlive) count++;
      return count
    }, 0);
  }
}

export class ZooState {
  @Selector()
  static animalsCount({ animals }): number {
    return ZooHelper.countAnimals(animals);
  }
}