Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(store): add tree shaking states into integration example #631

Closed
wants to merge 2 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions integration/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,7 @@ button {
margin: 0 auto;
border-top: solid 1px #ddd;
}

.menu a {
margin-right: 10px;
}
12 changes: 8 additions & 4 deletions integration/app/app.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@ import { Store, Select } from '@ngxs/store';
import { Observable } from 'rxjs';
import { FormBuilder, FormArray } from '@angular/forms';

import { AddTodo, RemoveTodo, TodoState, SetPrefix, TodosState, LoadData } from './todo.state';
import { AddTodo, RemoveTodo, TodoState } from './store/todo.state';
import { LoadData, SetPrefix, TodosState } from './store/todos.state';

@Component({
selector: 'app-root',
Expand Down Expand Up @@ -47,9 +48,12 @@ import { AddTodo, RemoveTodo, TodoState, SetPrefix, TodosState, LoadData } from
encapsulation: ViewEncapsulation.None
})
export class AppComponent {
@Select(TodoState) todos$: Observable<string[]>;
@Select(TodoState.pandas) pandas$: Observable<string[]>;
@Select(TodosState.pizza) pizza$: Observable<any>;
@Select(TodoState)
todos$: Observable<string[]>;
@Select(TodoState.pandas)
pandas$: Observable<string[]>;
@Select(TodosState.pizza)
pizza$: Observable<any>;

allExtras = [
{ name: 'cheese', selected: false },
Expand Down
9 changes: 4 additions & 5 deletions integration/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,20 +7,19 @@ import { NgxsStoragePluginModule } from '@ngxs/storage-plugin';
import { NgxsReduxDevtoolsPluginModule } from '@ngxs/devtools-plugin';
import { NgxsFormPluginModule } from '@ngxs/form-plugin';
import { NgxsLoggerPluginModule } from '@ngxs/logger-plugin';

import { environment } from '../environments/environment';
import { AppComponent } from './app.component';
import { MenuComponent } from './menu.component';
import { routes } from './app.routes';
import { states } from './app.state';

@NgModule({
declarations: [AppComponent],
declarations: [AppComponent, MenuComponent],
imports: [
BrowserModule,
FormsModule,
ReactiveFormsModule,
RouterModule.forRoot(routes),
NgxsModule.forRoot(states),
RouterModule.forRoot(routes, { useHash: true }),
NgxsModule.forRoot(),
NgxsStoragePluginModule.forRoot({
key: ['todos.todo']
}),
Expand Down
3 changes: 2 additions & 1 deletion integration/app/app.routes.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
import { Routes } from '@angular/router';
import { MenuComponent } from './menu.component';

export const routes: Routes = [
{ path: '', pathMatch: 'full', redirectTo: '/list' },
{ path: '', component: MenuComponent },
{ path: 'list', loadChildren: './list/list.module#ListModule' },
{ path: 'detail', loadChildren: './detail/detail.module#DetailModule' }
];
7 changes: 0 additions & 7 deletions integration/app/app.state.ts

This file was deleted.

4 changes: 4 additions & 0 deletions integration/app/detail/detail.actions.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
export class DetailFooActions {
public static readonly type: string = '[DetailActions] description';
constructor(public foo: boolean) {}
}
18 changes: 16 additions & 2 deletions integration/app/detail/detail.component.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,23 @@
import { Component } from '@angular/core';
import { Select, Store } from '@ngxs/store';
import { DetailFooActions } from './detail.actions';
import { DetailState, DetailStateModel } from './detail.state';
import { Observable } from 'rxjs';

@Component({
selector: 'app-detail',
template: `
<a [routerLink]="['/list']">List</a>
<a [routerLink]="['/list']">List</a> {{ detail$ | async | json }}
<br><br><button (click)="updateFoo()">Update foo</button>
`
})
export class DetailComponent {}
export class DetailComponent {
@Select(DetailState)
detail$: Observable<DetailStateModel>;

constructor(private store: Store) {}

public updateFoo() {
this.store.dispatch(new DetailFooActions(false));
}
}
3 changes: 1 addition & 2 deletions integration/app/detail/detail.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { DetailComponent } from './detail.component';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { NgxsModule } from '@ngxs/store';
import { DetailState } from './detail.state';
import { routes } from './detail.routes';

@NgModule({
imports: [CommonModule, RouterModule.forChild(routes), NgxsModule.forFeature([DetailState])],
imports: [CommonModule, RouterModule.forChild(routes), NgxsModule.forFeature()],
declarations: [DetailComponent]
})
export class DetailModule {}
20 changes: 16 additions & 4 deletions integration/app/detail/detail.state.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,19 @@
import { State } from '@ngxs/store';
import { Action, State, StateContext } from '@ngxs/store';
import { DetailModule } from './detail.module';
import { DetailFooActions } from './detail.actions';

@State({
export interface DetailStateModel {
foo: boolean;
}

@State<DetailStateModel>({
name: 'detail',
defaults: { foo: true }
defaults: { foo: true },
providedIn: DetailModule
})
export class DetailState {}
export class DetailState {
@Action(DetailFooActions)
changeFoo(ctx: StateContext<DetailStateModel>, { foo }: DetailFooActions) {
ctx.setState({ foo });
}
}
3 changes: 1 addition & 2 deletions integration/app/list/list.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,10 @@ import { ListComponent } from './list.component';
import { CommonModule } from '@angular/common';
import { RouterModule } from '@angular/router';
import { NgxsModule } from '@ngxs/store';
import { ListState } from './list.state';
import { routes } from './list.routes';

@NgModule({
imports: [CommonModule, RouterModule.forChild(routes), NgxsModule.forFeature([ListState])],
imports: [CommonModule, RouterModule.forChild(routes), NgxsModule.forFeature()],
declarations: [ListComponent]
})
export class ListModule {}
4 changes: 3 additions & 1 deletion integration/app/list/list.state.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
import { State, Selector } from '@ngxs/store';
import { ListModule } from './list.module';

@State({
name: 'list',
defaults: ['foo']
defaults: ['foo'],
providedIn: ListModule
})
export class ListState {
@Selector()
Expand Down
13 changes: 13 additions & 0 deletions integration/app/menu.component.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Component } from '@angular/core';

@Component({
selector: 'app-menu',
template: `
<p class="menu">
<b>Menu</b>:
<a [routerLink]="['/list']">List</a>
<a [routerLink]="['/detail']">Detail</a>
</p>
`
})
export class MenuComponent {}
34 changes: 34 additions & 0 deletions integration/app/store/todo.state.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { Action, Selector, State, StateContext } from '@ngxs/store';

export class AddTodo {
static type = 'AddTodo';

constructor(public readonly payload: string) {}
}

export class RemoveTodo {
static type = 'RemoveTodo';

constructor(public readonly payload: number) {}
}

@State<string[]>({
name: 'todo',
defaults: []
})
export class TodoState {
@Selector()
static pandas(state: string[]) {
return state.filter(s => s.indexOf('panda') > -1);
}

@Action(AddTodo)
addTodo({ getState, setState }: StateContext<string[]>, { payload }: AddTodo) {
setState([...getState(), payload]);
}

@Action(RemoveTodo)
removeTodo({ getState, setState }: StateContext<string[]>, { payload }: RemoveTodo) {
setState(getState().filter((_, i) => i !== payload));
}
}
Original file line number Diff line number Diff line change
@@ -1,45 +1,13 @@
import { State, Action, StateContext, Selector } from '@ngxs/store';
import { Action, Selector, State } from '@ngxs/store';
import { of } from 'rxjs';
import { tap } from 'rxjs/operators';

export class AddTodo {
static type = 'AddTodo';

constructor(public readonly payload: string) {}
}

export class RemoveTodo {
static type = 'RemoveTodo';

constructor(public readonly payload: number) {}
}
import { TodoState } from './todo.state';

export class TodoStateModel {
todo: string[];
pizza: { model: any };
}

@State<string[]>({
name: 'todo',
defaults: []
})
export class TodoState {
@Selector()
static pandas(state: string[]) {
return state.filter(s => s.indexOf('panda') > -1);
}

@Action(AddTodo)
addTodo({ getState, setState }: StateContext<string[]>, { payload }: AddTodo) {
setState([...getState(), payload]);
}

@Action(RemoveTodo)
removeTodo({ getState, setState }: StateContext<string[]>, { payload }: RemoveTodo) {
setState(getState().filter((_, i) => i !== payload));
}
}

export class SetPrefix {
static type = 'SetPrefix';
}
Expand All @@ -54,7 +22,8 @@ export class LoadData {
todo: [],
pizza: { model: undefined }
},
children: [TodoState]
children: [TodoState],
providedIn: 'ngxsRoot'
})
export class TodosState {
@Selector()
Expand Down
14 changes: 13 additions & 1 deletion packages/store/src/decorators/state.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Type } from '@angular/core';

import { ensureStoreMetadata } from '../internal/internals';
import { StoreOptions, META_KEY } from '../symbols';
import { META_KEY, StoreOptions } from '../symbols';
import { NgxsStateProvidersModule } from '../internal/state-providers/state-providers.module';

const stateNameRegex = new RegExp('^[a-zA-Z0-9]+$');

Expand Down Expand Up @@ -31,6 +34,15 @@ export function State<T>(options: StoreOptions<T>) {
meta.defaults = options.defaults;
meta.name = options.name;

if (options.providedIn) {
const provideIn: string | Type<unknown> = options.providedIn;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Change to providedIn

const type: Type<unknown> = typeof provideIn !== 'string' ? provideIn : null;
const children = options.children || [];
const states = [target, ...children];
states.forEach((state: Type<unknown>) => NgxsStateProvidersModule.provideInNgxsModule(state, type));
NgxsStateProvidersModule.defineStatesByProvideIn(provideIn, states);
}

if (!options.name) {
throw new Error(`States must register a 'name' property`);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Type } from '@angular/core';

export type NgxsStateType = Type<unknown>;

export enum NgxsProvidedIn {
root = 'ngxsRoot',
feature = 'ngxsFeature'
}

export interface NgxsProvides {
ngxsRoot: NgxsStateType[];
ngxsFeature: NgxsStateType[];
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import { Injectable, NgModule, Type } from '@angular/core';
import { NgxsProvidedIn, NgxsProvides, NgxsStateType } from './state-providers.interfaces';

@NgModule()
export class NgxsStateProvidersModule {
public static states: NgxsProvides = {
ngxsRoot: [],
ngxsFeature: []
};

public static provideInNgxsModule(target: NgxsStateType, type: Type<unknown> = null): void {
const { ngxsRoot, ngxsFeature } = NgxsStateProvidersModule.states;
const firstInitialize = ![...ngxsRoot, ...ngxsFeature].includes(target);

if (firstInitialize) {
Injectable({ providedIn: type || NgxsStateProvidersModule })(target);
}
}

public static defineStatesByProvideIn(provideIn: string | Type<unknown>, states: NgxsStateType[]): void {
const stateSourceKey = typeof provideIn === 'string' ? provideIn : NgxsProvidedIn.feature;
const sources = NgxsStateProvidersModule.states[stateSourceKey] || [];
sources.push(...states);
}

public static filterWithoutDuplicate(states: NgxsStateType[], sources: NgxsStateType[] = []): NgxsStateType[] {
return states.filter((value, index) => {
const nonDuplicateSource = states.indexOf(value) === index;
const nonDuplicateTarget = !sources.includes(value);
return value && nonDuplicateSource && nonDuplicateTarget;
});
}

public static flattenedUniqueStates(entry: NgxsStateType[], compare: NgxsStateType[]) {
return this.filterWithoutDuplicate([...entry, ...compare]);
}
}
Loading