wizardnet972

wizardnet972

#angular freelance consultant

Member Since 8 years ago

@wizardnet972, Israel

Experience Points
39
follower
Lessons Completed
58
follow
Lessons Completed
287
stars
Best Reply Awards
130
repos

102 contributions in the last year

Pinned
⚡ ⚙️ Critical plugin for webpack (https://webpack.js.org/)
⚡ 🎮 Implementation of Tic-Tac-Toe Game with ngrx state-management
⚡ Memory game in ngrx
⚡ this is code from my talk about angular-elements in depth (lite version)
Activity
May
11
1 week ago
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

One more thing: when error is throw the entire action is stopped (can't emit the action again) unless using catchError in the pipeline:

 saveData() {
   throw new Error('bad');
   ...
 }
 ...
 readonly action1$ = this.ui.action1$.pipe(
    exhaustMap(() =>
      this.api.saveData().pipe(
        switchMap(() => ...
     ....   
May
9
1 week ago
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

How to hook to another action in the PoC RxSignals?

Hi @BioPhoton ,

The PR https://github.com/rx-angular/rx-angular/pull/1112 introduce actions:

 ui = this.factory.create({..});
 ....
 readonly create$ = this.ui.create$.pipe(...) 

create$ is AnonymousSubject.

I want to be able to hook/switch to this action from another action pipeline (to avoid duplicate logic):

 ui = this.factory.create({..});
 ....
 readonly create$ = this.ui.create$.pipe(...) 

 readonly dialog$ = this.ui.openDialog$.pipe(
   map(() => this.dialog.open(...)),
   switchMap(() => this.create$.pipe(tap(() => console.log('after')))  
 )

But this way it doesn't work. I have tried mergeMap, switchMapTo etc. still does not work.

Do you know how to make it work? Is there a way or operator to invoke the create$ from dialog$ without calling this.ui.create()? because this.ui.create() doesn't have an indication of when the pipeline is done

wizardnet972
wizardnet972

@BioPhoton I split them into two parts: dialog flow and create flow. I don't want to combine them because I can run create flow without the dialog flow in other cases. And split into smaller pipelines to make the code more readable.

But the problem is to return to the dialog flow to close the dialog. because the create can failed and if I close it before then all my data will be lost. so only when create flow is done then I want to return to dialog flow and close the dialog.

There is a way using rx-angular actions to do that?

Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

Another issue I have is to run another action and wait for a response:

You can see it in stackblitz example I made

From action1 I want to trigger action2 and after that return to action1 after the pipeline of action2 is done.

 readonly action1$ = this.ui.action1$.pipe(
    exhaustMap(() =>
      this.api.saveData().pipe(
        switchMap(() =>
          this.api.saveData2().pipe(
            tap({
              next: () => {
                console.log('NOTIFICATION FROM ACTION1: save');

                // this.ui.action2() after that log into the console.
                /**
                 * this.ui.action2().pipe(tap(() => console.log('after action 2')))
                 */
              },
            })
          )
        ),
        catchError((e) => {
          console.log('ERROR!!');
          return of(e);
        })
      )
    )
  );
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

@BioPhoton can you implement tapResponse function from ngrx/component-store?

https://github.com/ngrx/platform/blob/master/modules/component-store/src/tap-response.ts

Because right now, the actions lead to error-boilerplate and the file is too long:

see stackblitz example I made

 readonly action1$ = this.ui.action1$.pipe(
    exhaustMap(() =>
      this.api.saveData().pipe(
        switchMap(() =>
          this.api.saveData2().pipe(
            tap({
              next: () => {
                console.log('NOTIFICATION: save');
              },
            })
          )
        ),
        catchError((e) => {
          console.log('ERROR!!');
          return of(e);
        })
      )
    )
  );

with tapResponse:

readonly action1$ = this.ui.action1$.pipe(
    exhaustMap(() =>
      this.api.saveData().pipe(
        switchMap(() => this.api.saveData2()),
        tapResponse(
          () => console.log('NOTIFICATION: save'),
          () => console.log('ERROR!!')
        )
      )
    )
  );
May
5
1 week ago
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

How can I tell when the action is complete the operators pipeline?

I only dispatch an action: ui.create(...); but from wherever I did the dispatch I also want to know when it's complete the command pipeline.

How I do it using this version of rx-actions?

I suggest to return the subject itself to continue the pipeline. ui.create(...).pipe(...)

May
4
2 weeks ago
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

Hi, @BioPhoton can you add the next feature to actions?

Consider please when I want to do something after the actions for example to close the dialog. I can't do it before because if I close the dialog and the operation failed all my form will be gone.

Take a look at create notification example please: after I create a notification I want to close the dialog by calling to close function.

// dashboard.facade.ts:

export interface UiActions { 
 createNotification: any;
}

 ui = this.factory.create();
 ...
 readonly createNotification$ = this.ui.createNotification$.pipe(
   exhustMap(notification => this.api.createNotification(notification))
 );

 ctor(..) {
   this.hold(createNotificartion$);
 }

dashboard.component.ts:

 class DahsboardComponent {
    ...
    openNotification() {
       this.dialogRef.open(...);
       this.dialogRef.componentInstance.fields = fields;
       this.dashboardFacade.hold( this.dialogRef.submitForm, (form) => this.ui.createNotification(form)); // here missing when the action is complete to close the dialog: dialogRef.close();
    }
 }

What I suggest just to add next observable to each action:

   openNotification() {
       this.dialogRef.open(...);
       this.dialogRef.componentInstance.fields = fields;

       this.ui.createNotificationNext$.subscribe(_ => { this.dialogRef.close() }); ///<----- this

       this.dashboardFacade.hold( this.dialogRef.submitForm, (form) => this.ui.createNotification(form)); // here missing when the action is complete to close the dialog: dialogRef.close();
    }
Apr
24
3 weeks ago
Activity icon
issue

wizardnet972 issue angular-architects/nx-ddd-plugin

wizardnet972
wizardnet972

bug: when create feature with directory flag the files got wrong due to missing using with prefix

When I run commands to create features in different directories (with the same name) the command is successfully executed:

yarn nx g @angular-architects/ddd:feature --app ddd-ui --lazy --directory foo --name view --domain booking
yarn nx g @angular-architects/ddd:feature --app ddd-ui --lazy --directory bar --name view --domain booking

But inside the domain lib, only one view.facade.ts was created. and in the index.ts is exporting files that do not exist: https://github.com/wizardnet972/nx-ddd/blob/main/libs/booking/domain/src/index.ts#L3

export * from './lib/booking-domain.module';
export * from './lib/application/foo-view.facade';
export * from './lib/application/bar-view.facade';
image

Suggested Solution: add directory prefix to facade files including module names.

When I create a feature with directory flag, the files:

Reproduce:

git clone https://github.com/wizardnet972/nx-ddd.git
yarn
check the domain file.

OR

 npx create-nx-workspace
✔ Workspace name (e.g., org name)     · nx-ddd
✔ What to create in the new workspace · angular
✔ Application name                    · ddd-ui
✔ Default stylesheet format           · scss

yarn add @angular-architure/ddd
yarn nx g @angular-architects/ddd:domain booking
yarn nx g @angular-architects/ddd:feature --app ddd-ui --lazy --directory foo --name view --domain booking
yarn nx g @angular-architects/ddd:feature --app ddd-ui --lazy --directory bar --name view --domain booking
Activity icon
created branch
createdAt 3 weeks ago
Activity icon
created repository
createdAt 3 weeks ago
Apr
15
1 month ago
Apr
8
1 month ago
Activity icon
issue

wizardnet972 issue nrwl/nx

wizardnet972
wizardnet972

secondary entry points for workspace/node library

Description

I would like to create a lib that contains angular and nodejs code like so:

products <-- workspace/nodejs package
  api  <--- node package (server)
  data-access <-- angular package (angular services only)
  feature-dashboard <-- angular package
  ui-dashboard <-- angular package 
  src
    lib <-- common things that shared between nodejs and angular (server and client) models, utils...
      entity-model.ts
       ...
   package.json

Imports:

import { entityModel } from '@org/products'; <--- I can import only the src folder.
import { ... } from '@org/products/api';  <--- I can import only the api folder.
import { ... } from '@org/products/feature-dashboard';

Motivation

I have been trying to get on board using wrap those libraries with a directory - which is a good solution. But I think it's better implementation and better code structure in order to wrap the angular and nodejs code together. And this way I can import things from the correct folders.

Suggested Implementation

just add secondary entry points to nodejs/workspace schematics - they should act just like angular secondary entry points that create the lib inside another lib, and add paths to tsconfig.

Alternate Implementations

Mar
30
1 month ago
Activity icon
issue

wizardnet972 issue comment rx-angular/rx-angular

wizardnet972
wizardnet972

feat: PoC RxSignals

Often setters are enough. In such cases, we can use state.set({prop}).

If we want to apply behaviour we need to get a stream. This often leads to bloated code with Subjects.

@Component({
template: `
<input (input)="searchBtn.next($event.target.value)" /> {{search$ | async}}
<button (click)="submitBtn.next()" >Submit<button>
`
})
class Component {

  _submitBtn = new Sunject<void>();
  _search = new Sunject<string>();
  
  set submitBtn() {
      _submitBtn.next()
  }
  set search(search: string) {
      _search.next(search)
  }
  get search$() {
      return _search.asObservable();
  }
  
  refresh$ = this. search$.pipe(
    switchMap(fetchData)
  )

}

We could use a Proxy object and a little TS to reduce the boilerplate here:

@Component({
template: `
<input (input)="uiActions.searchBtn($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

If we use the actions only in the class with RxEffects or RxState we won't cause any memory leaks.

@Component({
template: `
<input (input)="uiActions.search($event.target.value)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>()
  
  refresh$ = this. uiActions.search$.pipe(
    switchMap(fetchData)
  )

}

A config object could even get rid of the mapping in the template:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private effects: RxEffects, private state: RxState<{list: any[]}>) {
    this.state('list', this. uiActions.search$.pipe(switchMap(fetchData)));
    this.effects(this.uiActions.search$, fetchData);
  }

}

As we have no subscriber after the component is destroyed we don't need to complete the Subjects.

However if we would pass it to another service which has a longer life time we could create a memory leak:

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects]
})
class Component {
  
  uiActions = getActions<{submit: viod, search: string}>({search: (e) => e.target.value})
  
  constructor( private globalService: GlobalService) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

Here, as the global service lives longer than the component we have subscribers on the subject after the component is destroyed. For such situations, a hook is needed to get the component destroyed event.


Update: Due to typing issues, I had to refactor to an architecture where we have a wrapper scope for the action create function. Incredible BIG THANKS to @ddprrt for the support here.

The current solution provides a service and a factory function.

The service is hooked into Angular's life-cycles and so it cleans up on destruction. The factory function returns a pair of functions, create and destroy that can be used manually.

Service:

export class RxActionFactory<T extends Actions> implements OnDestroy {
  private readonly subjects: SubjectMap<T> = {} as SubjectMap<T>;
  create<U extends ActionTransforms<T> = {}>(transforms?: U): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(this.subjects, transforms)
    ) as RxActions<T, U>;
  }

  destroy() {
    for (let subjectsKey in this.subjects) {
      this.subjects[subjectsKey].complete();
    }
  }

  ngOnDestroy() {
    this.destroy();
  }
}

Service Usage:

interface StateActions {
  refreshList: viod,
}

@Injectable({providedIn: 'root'})
class GlobalState implements OnDestroy {
    _fac = new RxActionFactory<StateActions >();
    uiActions = this._fac.create()
    
    ngOnDestroy() {
        this._fac.destroy();
    }

Component Usage:

interface UIActions {
  submit: viod, 
  search: string
}

@Component({
template: `
<input (input)="uiActions.search($event)" /> {{search$ | async}}
<button (click)="uiActions.submit()" >Submit<button>
`,
providers: [ RxState, RxEffects, RxActionFactory]
})
class Component {
  
  uiActions = this.actionFactory.create({
    search: (e: Event | string | number) => e.target.value !== undefined ? e.target.value + '' : e + ''
  })
  
  constructor( 
     private globalService: GlobalService,
     private actionFactory: RxActionFactory<UIActions>
  ) {
    this.globalService.connectSearchTrigger(this.uiActions.search$);
  }

}

The factory function is maybe irrelevant for Angular but still worth looking at it:

export function rxActionCreator<T extends {}>() {
  const subjects = {} as SubjectMap<T>;
  return {
    create,
    destroy: (): void => {
      for (let subjectsKey in subjects) {
        subjects[subjectsKey].complete();
      }
    },
  };

  function create<U extends ActionTransforms<T> = {}>(
    transforms?: U
  ): RxActions<T, U> {
    return new Proxy(
      {} as RxActions<T, U>,
      actionProxyHandler(subjects, transforms)
    ) as RxActions<T, U>;
  }
}

Usage:

type UIActions = {
  search: string;
  check: number;
};

const {create, destroy} = rxActionCreator<UIActions>();
const actions = create({
  search: (v: number) => 'string',
});
create.search(4);

Update:

I removed the function because we can also do new RxActionsFactory<any>().create(). Additionally, docus are now present as well as tests.

Missin features:

  • Angular Error handler

Missing tests:

  • subscribe to a subject that is not emitted on
  • emit on a property without subscriber
  • error in transform

The only flaw we could maybe see here is the wrapper e.g. new RxActionsFactory().create().prop

For now I have no clue how we could get rid of it and expose the setter and observables directly e.g. new RxActionsFactory().prop. One problem is typescript, the other Proxies, classes and typescript :). Maybe problems to solve in a later step...

Related issues: #423, #1013

wizardnet972
wizardnet972

Hey @BioPhoton ,

Why when I add startWith to load$ pipe - exhaustMap does not work? endWith also does not work.

I made an example application on stackblitz with the last update files from your POC: https://stackblitz.com/edit/angular-ivy-z6ca6k?file=src%2Fapp%2Ffoo.service.ts

You can see the state as json in the output.

export interface UiActions {
  load: void;
}

@Injectable({ providedIn: 'root' })
export class AppFacade extends RxState<{ items: any[]; pending: boolean }> {
  ui = this.factory.create();

  load$ = this.ui.load$.pipe(
    exhaustMap(() =>
      of({ items: [{ id: 1 }, { id: 2 }] }).pipe(delay(4 * 1000))
    ),
    startWith({ pending: true }),
    endWith({ pending: false })
  );

  constructor(private factory: RxActionFactory<UiActions>) {
    super();
    this.connect(this.load$);
  }
}
Mar
29
1 month ago
Mar
21
1 month ago
Activity icon
issue

wizardnet972 issue comment bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

https://stackblitz.com/edit/js-2bmswj

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
wizardnet972
wizardnet972

@bluetech I was able to preserve the comment and I put the comment at above the class line and it's work.

Thank you for your help and your time :))))

Activity icon
issue

wizardnet972 issue bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

https://stackblitz.com/edit/js-2bmswj

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
Activity icon
issue

wizardnet972 issue comment bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

https://stackblitz.com/edit/js-2bmswj

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
wizardnet972
wizardnet972

@bluetech the comment solves the problem /* @ngInject */. but unfortunately, the typescript compiler remove comments. typescript transpile to es6. then I run the ng-annotate-patched.

if you have another alternative solution to the comment I would really appriciate it

Thank you!

Activity icon
issue

wizardnet972 issue comment bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

https://stackblitz.com/edit/js-2bmswj

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
wizardnet972
wizardnet972

@bluetech yes it's was helpful :) But I have noticed if the ngInject; doesn't at the first line, it doesn't work. The problem is typescript is transpile the private to this.... in the first line and push the ngInject- which make the ng-annotate-patched to not work:

 constructor(barService) {
    this.barService = barService; //<--- generate by typescript
    'ngInject';
    console.log('in foo service ctor', barService);
  }

Not generate $inject.

original code:

 constructor(private barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }

https://stackblitz.com/edit/js-2bmswj

Activity icon
issue

wizardnet972 issue comment bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

https://stackblitz.com/edit/js-2bmswj

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
wizardnet972
wizardnet972

@bluetech I change constructor(barService: BarService) { to constructor(barService) {. not work. I try to change to constructor(BarService) { still doesn't work. After I remove the : BarService it's now es6 code.

Activity icon
issue

wizardnet972 issue bluetech/ng-annotate-patched

wizardnet972
wizardnet972

ngInject not work for class constructor

when I run node app.js I got no change in the class constructor where I write ngInject. why?

app.js

const patched = require('ng-annotate-patched');

const content = `
import * as angular from 'angular';

export class BarService {
  name = 'bar';

  constructor() {
    'ngInject';

    console.log('in bar');
  }
}

export class FooService {
  name = 'foo';

  constructor(barService: BarService) {
    'ngInject';
    console.log('in foo service ctor', barService);
  }
}

const modulex = angular
  .module('foo', [])
  .service('barService', BarService)
  .service('fooService', FooService)
  .run((fooService) => {
    'ngInject';

    console.log({ fooService });
  }).name;

const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;

angular.bootstrap(document.body, [bootstrapModuleName]);
`;
const a = patched(content, {});

console.log({ a });

The output as you can see doesn't do inject within the class constructor.

a: {
    src: '\n' +
      "import * as angular from 'angular';\n" +
      '\n' +
      'export class BarService {\n' +
      "  name = 'bar';\n" +
      '\n' +
      '  constructor() {\n' +
      "    'ngInject';\n" +
      '\n' +
      "    console.log('in bar');\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'export class FooService {\n' +
      "  name = 'foo';\n" +
      '\n' +
      '  constructor(barService: BarService) {\n' +
      "    'ngInject';\n" +
      "    console.log('in foo service ctor', barService);\n" +
      '  }\n' +
      '}\n' +
      '\n' +
      'const modulex = angular\n' +
      "  .module('foo', [])\n" +
      "  .service('barService', BarService)\n" +
      "  .service('fooService', FooService)\n" +
      '  .run((fooService) => {\n' +
      "    'ngInject';\n" +
      '\n' +
      '    console.log({ fooService });\n' +
      '  }).name;\n' +
      '\n' +
      "const bootstrapModuleName = angular.module('application.bootstrap', [modulex]).name;\n" +
      '\n' +
      'angular.bootstrap(document.body, [bootstrapModuleName]);\n'
  }
}
Mar
11
2 months ago
Activity icon
issue

wizardnet972 issue comment nrwl/nx

wizardnet972
wizardnet972

Error: EEXIST: file already exists, mkdir on build default project

Current Behavior

Nx runs unnecessary build to defaultProject (nx.json) when affected:libs returns "workspace". If I have defaultProject I got an error:

Warning: bundle initial exceeded maximum budget. Budget 500.00 kB was not met by 55.84 kB with a total of 555.84 kB.

Unexpected error:
[Error: EEXIST: file already exists, mkdir '/Users/wizardnet972/code/nx-repo/node_modules/.cache/nx/d0432d082f39baf22e4b7a034c702ff9969651c2a5a56dc58172c62528eb8e0b/outputs/dist/out-tsc'] {
  errno: -17,
  code: 'EEXIST',
  syscall: 'mkdir',
  path: '/Users/wizardnet972/code/nx-repo/node_modules/.cache/nx/d0432d082f39baf22e4b7a034c702ff9969651c2a5a56dc58172c62528eb8e0b/outputs/dist/out-tsc'
}
error Command failed with exit code 1.

I run build on app-a and app-b (run-many) but the default is app-c.

Expected Behavior

if I don't have defaultProject then no build should run.

Steps to Reproduce

This issue may not be prioritized if details are not provided to help us reproduce the issue.

Failure Logs

Environment


  Node : 16.13.0
  OS   : darwin arm64
  yarn : 1.22.11

  nx : 13.3.0
  @nrwl/angular : 13.3.1
  @nrwl/cli : 13.3.0
  @nrwl/cypress : 13.3.0
  @nrwl/devkit : 13.3.0
  @nrwl/eslint-plugin-nx : 13.3.0
  @nrwl/express : undefined
  @nrwl/jest : 13.3.0
  @nrwl/linter : 13.3.0
  @nrwl/nest : undefined
  @nrwl/next : undefined
  @nrwl/node : 13.3.1
  @nrwl/nx-cloud : undefined
  @nrwl/react : undefined
  @nrwl/react-native : undefined
  @nrwl/schematics : undefined
  @nrwl/tao : 13.3.0
  @nrwl/web : undefined
  @nrwl/workspace : 13.3.0
  @nrwl/storybook : 13.3.1
  @nrwl/gatsby : undefined
  typescript : 4.4.4
  rxjs : 7.4.0
  ---------------------------------------
  Community plugins:
  	 @angular/animations: 13.1.0
  	 @angular/common: 13.1.0
  	 @angular/compiler: 13.1.0
  	 @angular/core: 13.1.0
  	 @angular/forms: 13.1.0
  	 @angular/platform-browser: 13.1.0
  	 @angular/platform-browser-dynamic: 13.1.0
  	 @angular/router: 13.1.0
  	 @ngrx/component-store: 13.0.2
  	 @ngrx/effects: 13.0.2
  	 @ngrx/entity: 13.0.2
  	 @ngrx/router-store: 13.0.2
  	 @ngrx/store: 13.0.2
  	 @angular-architects/ddd: 1.5.1
  	 @angular-devkit/build-angular: 13.0.4
  	 @angular/cdk: 13.1.0
  	 @angular/cli: 13.0.4
  	 @angular/compiler-cli: 13.1.0
  	 @angular/language-service: 13.1.0
  	 @angular/material: 13.1.0
  	 @jscutlery/semver: 2.15.0
  	 @ngneat/input-mask: 5.0.1
  	 @ngneat/scam: 3.0.4
  	 @ngrx/data: 13.0.2
  	 @ngrx/schematics: 13.0.2
  	 @ngrx/store-devtools: 13.0.2
  	 @rx-angular/template: 1.0.0-beta.29
  	 @storybook/angular: 6.4.9
  	 nx-stylelint: 13.1.0
wizardnet972
wizardnet972

@FrozenPandaz Hey :) I'm still trying to reproduce this issue. I think it's happened because of resource collision.

I debug the nx code though run-many.js and I found out that something is failed in finally section: https://github.com/nrwl/nx/blob/4b21ab9502f8fcc4c20db3478adcb7aa5f7a934a/packages/workspace/src/tasks-runner/default-tasks-runner.ts#L60

so the error caught in the catch section: https://github.com/nrwl/nx/blob/4b21ab9502f8fcc4c20db3478adcb7aa5f7a934a/packages/workspace/src/tasks-runner/default-tasks-runner.ts#L56

Not yet sure why.

But as far I can see I add project.json to the root project and add the entry in workspace.json.

When I run yarn nx run-many --target build --all, nx detect also "workspace" project. which triggers to build of the default project.

So I think because nx run the commands in parallel the resources of the cache/directory is occupied (in use/exist).

Think about this, I build every project including the default project. also nx build the workspace project which is the default project in parallel. same cache same files and directory.

Maybe the affected and the graph workspace should not build the default project. also what if I don't have a default project?

I had this problem for months. I will appreciate your help in this matter :)

Activity icon
issue

wizardnet972 issue nrwl/nx

wizardnet972
wizardnet972

Some dependencies of 'libb-api' have not been built. This probably due to the build target being misconfigured

Current Behavior

When I run:

yarn nx run-many --target build --all

or:

yarn nx run-many --target build --all --skip-nx-cache

I got error:

  Some of the project libb-api's dependencies have not been built yet. Please build these libraries before:
       - libc-api

       Try: nx run libb-api:build --with-deps
       Some dependencies of 'libb-api' have not been built. This probably due to the build target being misconfigured.

in nx 13.8.7.

Expected Behavior

nx run-many should build all projects

Steps to Reproduce

My project is simple: create app preset: nx-app (node app) then create three libs: liba-api, libb-api, libc-api (node lib buildable)

Or clone:

git clone https://github.com/wizardnet972/nx-cache.git
yarn
yarn nx run-many --target build --all

Failure Logs

$ /Users/wizardnet972/sandbox/nx-cache/node_modules/.bin/nx run-many --target build --all

    ✔  nx run libc-api:build (691ms)

    ✖  nx run libb-api:build
       Some of the project libb-api's dependencies have not been built yet. Please build these libraries before:
       - libc-api

       Try: nx run libb-api:build --with-deps
       Some dependencies of 'libb-api' have not been built. This probably due to the build target being misconfigured.

    ✔  nx run workspace:build (4s)

 ——————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————

 >  NX   Ran target build for 5 projects (4s)

    ✖    1/3 failed
    ✔    2/3 succeeded [0 read from cache]

Environment


 >  NX   Report complete - copy this into the issue template

   Node : 16.13.0
   OS   : darwin arm64
   yarn : 1.22.11

   nx : 13.8.7
   @nrwl/angular : undefined
   @nrwl/cli : 13.8.7
   @nrwl/cypress : undefined
   @nrwl/detox : undefined
   @nrwl/devkit : 13.8.7
   @nrwl/eslint-plugin-nx : 13.8.7
   @nrwl/express : undefined
   @nrwl/jest : 13.8.7
   @nrwl/js : 13.8.7
   @nrwl/linter : 13.8.7
   @nrwl/nest : undefined
   @nrwl/next : undefined
   @nrwl/node : 13.8.7
   @nrwl/nx-cloud : undefined
   @nrwl/react : undefined
   @nrwl/react-native : undefined
   @nrwl/schematics : undefined
   @nrwl/storybook : undefined
   @nrwl/tao : 13.8.7
   @nrwl/web : undefined
   @nrwl/workspace : 13.8.7
   typescript : 4.5.5
   rxjs : 6.6.7
   ---------------------------------------
   Community plugins:

(node:74635) [DEP0148] DeprecationWarning: Use of deprecated folder mapping "./" in the "exports" field module resolution of the package at /Users/wizardnet972/sandbox/nx-cache/node_modules/tslib/package.json.
Update this package.json to use a subpath pattern like "./*".
Activity icon
created branch
createdAt 2 months ago
Activity icon
created repository
createdAt 2 months ago
Previous