ngrx / platform

Reactive State for Angular
https://ngrx.io
Other
8.03k stars 1.97k forks source link

Bug(@ngrx/Effect): Cannot read property 'ofType' of undefined with -AOT Compiler Angular 5 #963

Closed AnthonyQrea closed 6 years ago

AnthonyQrea commented 6 years ago

I'm submitting a...


[ ] Regression (a behavior that used to work and stopped working in a new release)
[X] Bug report  
[ ] Feature request
[ ] Documentation issue or request

What is the current behavior?

Hello ,sorry for my bad English When I compile my application in -AOT, the Angular console does not send me an error (compilation is good). But in my browser, I have this error "auth.effects.ts: 81 Uncaught TypeError: Can not read property 'ofType' of undefined"

Expected behavior:

I would like the error to disappear and the application will launch

Minimal reproduction of the problem with instructions:

I Have : My Auth.effect.ts :

import { Injectable } from '@angular/core';
import { Headers } from '@angular/http';

// import @ngrx
import { Effect, Actions, ofType } from '@ngrx/effects';
import { toPayload } from '@ngrx/effects/@ngrx/effects';
import { Action } from '@ngrx/store';
import { FacebookService, InitParams, LoginResponse } from 'ngx-facebook';

// import rxjs
import { Observable } from 'rxjs/Observable';
import 'rxjs/add/operator/debounceTime';
import 'rxjs/add/operator/map';
import 'rxjs/add/operator/switchMap';

import * as sdk from '../../shared/sdk';

// import actions
import {
  ActionTypes,
  AuthenticatedErrorAction,
  AuthenticatedSuccessAction,
  SignInErrorAction,
  SignInSuccessAction,
  SignOutErrorAction,
  SignOutSuccessAction,
  RegisterErrorAction,
  RegisterSuccessAction,
  SignInAction,
  RequestResetPasswordAction,
  RequestResetPasswordErrorAction,
  RequestResetPasswordSuccessAction,
  SetNewPasswordAction,
  SetNewPasswordErrorAction,
  SetNewPasswordSuccessAction,
  GetCurrentUserAction,
  GetCurrentUserErrorAction,
  GetCurrentUserSuccessAction,
  FacebookLoginSuccessAction,
  FacebookLogoutAction,
  FacebookLoginErrorAction,
  LoginNetEntrepriseSuccessAction,
  LoginNetEntrepriseErrorAction,
  FacebookLogoutSuccessAction,
  FacebookLogoutErrorAction,
  VerifiyEmailSuccessAction,
  VerifiyEmailErrorAction,
  SendVerifiyEmailSuccessAction,
  SendVerifiyEmailErrorAction,
  DeleteAccountAction,
  DeleteAccountErrorAction,
  DeleteAccountSuccessAction
} from './auth.actions';

/**
 * Effects offer a way to isolate and easily test side-effects within your
 * application.
 * The `toPayload` helper function returns just
 * the payload of the currently dispatched action, useful in
 * instances where the current state is not necessary.
 *
 * Documentation on `toPayload` can be found here:
 * https://github.com/ngrx/effects/blob/master/docs/api.md#topayload
 *
 * If you are unfamiliar with the operators being used in these examples, please
 * check out the sources below:
 *
 * Official Docs: http://reactivex.io/rxjs/manual/overview.html#categories-of-operators
 * RxJS 5 Operators By Example: https://gist.github.com/btroncone/d6cf141d6f2c00dc6b35
 */

@Injectable()
export class AuthEffects {

  /**
   * Authenticate user.
   */
  @Effect()
  public signIn: Observable<Action> = this.actions
    .ofType(ActionTypes.SIGN_IN)
    .debounceTime(500)
    .map((action: SignInAction) => action.payload)
    .switchMap((payload: any) => {
      return this.userService
        .login({
          email: payload.email,
          password: payload.password
        })
        .map(user => new SignInSuccessAction({ user: user }))
        .catch(error => Observable.of(new SignInErrorAction({ error: error })));
    });

  /**

...

 apply() { }

  /**
   * @constructor
   * @param {Actions} actions
   * @param {UserService} userService
   */
  constructor(
    private actions: Actions,
    private userService: sdk.CustomUserApi,
    private authService: sdk.LoopBackAuth,
    private _fb: FacebookService
  ) { }
}

my auth.action.ts :

// import @ngrx
import { Action } from '@ngrx/store';

// import type function
import { type } from '../../shared/util';
import * as sdk from '../../shared/sdk';
import * as shared from '../../shared';

export const ActionTypes = {
  CLEAR_AUTH: type('[auth] Clear auth'),
  SIGN_IN: type('[auth] Sign in'),
  SIGN_IN_ERROR: type('[auth] Sign in error'),
  SIGN_IN_SUCCESS: type('[auth] Sign in success'),
  AUTHENTICATED: type('[auth] Authenticated'),
  AUTHENTICATED_ERROR: type('[auth] Authenticated error'),
  AUTHENTICATED_SUCCESS: type('[auth] Authenticated success'),
  SIGN_OUT: type('[auth] Sign off'),
  SIGN_OUT_ERROR: type('[auth] Sign off error'),
  SIGN_OUT_SUCCESS: type('[auth] Sign off success'),
  REGISTER: type('[auth] Register'),
  REGISTER_ERROR: type('[auth] Register error'),
  REGISTER_SUCCESS: type('[auth] Register success'),
  REQUEST_RESET_PASSWORD: type('[auth] Request reset password'),
  REQUEST_RESET_PASSWORD_ERROR: type('[auth] Request reset password error'),
  REQUEST_RESET_PASSWORD_SUCCESS: type('[auth] Request reset password success'),
  SET_NEW_PASSWORD: type('[auth] Set new password'),
  SET_NEW_PASSWORD_SUCCESS: type('[auth] Set new password success'),
  SET_NEW_PASSWORD_ERROR: type('[auth] Set new password error'),
  GET_USER: type('[auth] Get current user'),
  GET_USER_SUCCESS: type('[auth] Get current user success'),
  GET_USER_ERROR: type('[auth] Get current user error'),
  FACEBOOK_LOGIN: type('[auth] Facebook login'),
  FACEBOOK_LOGIN_SUCCESS: type('[auth] Facebook login success'),
  FACEBOOK_LOGIN_ERROR: type('[auth] Facebook login error'),
  FACEBOOK_LOGOUT: type('[auth] Facebook logout'),
  FACEBOOK_LOGOUT_SUCCESS: type('[auth] Facebook logout success'),
  FACEBOOK_LOGOUT_ERROR: type('[auth] Facebook logout error'),
  LOGIN_NET_ENTREPRISE: type('[auth] Net entreprise login'),
  LOGIN_NET_ENTREPRISE_SUCCESS: type('[auth] Net entreprise login success'),
  LOGIN_NET_ENTREPRISE_ERROR: type('[auth] Net entreprise login error'),
  VERIFY_EMAIL: type('[auth] Verify user email'),
  VERIFY_EMAIL_SUCCESS: type('[auth] Verify user email success'),
  VERIFY_EMAIL_ERROR: type('[auth] Verify user email error'),
  SEND_VERIFY_EMAIL: type('[auth] Send verify user email'),
  SEND_VERIFY_EMAIL_SUCCESS: type('[auth] Send verify user email success'),
  SEND_VERIFY_EMAIL_ERROR: type('[auth] Send verify user email error'),
  DELETE_ACCOUNT: type('[auth] delete account'),
  DELETE_ACCOUNT_SUCCESS: type('[auth] delete account success'),
  DELETE_ACCOUNT_ERROR: type('[auth] delete account error')
};

export interface Action {
  type: string;
  payload?: any;
}

/**
 * Clear.
 * @class ClearAction
 * @implements {Action}
 */
export class ClearAction implements Action {
  public type: string = ActionTypes.CLEAR_AUTH;
  constructor(public payload?: any) { }
}

/**
 * Sign in.
 * @class SignInAction
 * @implements {Action}
 */
export class SignInAction implements Action {
  public type: string = ActionTypes.SIGN_IN;
  constructor(public payload: { email: string, password: string }) { }
}
....

/**
 * Actions type.
 * @type {Actions}
 */
export type Actions =
  SignInAction
  | AuthenticatedAction
  | AuthenticatedErrorAction
  | AuthenticatedSuccessAction
  | SignInErrorAction
  | SignInSuccessAction
  | RegisterAction
  | RegisterErrorAction
  | RequestResetPasswordAction
  | RequestResetPasswordErrorAction
  | RequestResetPasswordSuccessAction
  | RegisterSuccessAction
  | SetNewPasswordAction
  | SetNewPasswordErrorAction
  | SetNewPasswordSuccessAction
  | GetCurrentUserAction
  | GetCurrentUserErrorAction
  | FacebookLoginAction
  | FacebookLoginSuccessAction
  | FacebookLoginErrorAction
  | FacebookLogoutAction
  | GetCurrentUserSuccessAction
  | LoginNetEntrepriseAction
  | LoginNetEntrepriseErrorAction
  | LoginNetEntrepriseSuccessAction
  | FacebookLogoutErrorAction
  | FacebookLogoutSuccessAction
  | VerifiyEmailErrorAction
  | VerifiyEmailSuccessAction
  | VerifyEmailAction
  | SendVerifyEmailAction
  | SendVerifiyEmailErrorAction
  | SendVerifiyEmailSuccessAction
  | DeleteAccountAction
  | DeleteAccountErrorAction
  | DeleteAccountSuccessAction
  | ClearAction
  ;

my app.module.ts :

import { BrowserModule } from '@angular/platform-browser';
import {
  NgModule,
  TRANSLATIONS,
  TRANSLATIONS_FORMAT,
  LOCALE_ID,
  APP_BOOTSTRAP_LISTENER,
  InjectionToken,
  Inject,
  Type
} from '@angular/core';

import { FormsModule } from '@angular/forms';

import '../app/shared/rxjs-operators';
import { AppComponent } from './app.component';

// START STORE
import { StoreModule } from '@ngrx/store';
// import { reducers, State } from './app.reducers';

import * as core from './core';

import { StoreRouterConnectingModule } from '@ngrx/router-store';
import { EffectsModule, EffectSources } from '@ngrx/effects';
// END STORE

// START LOOPBACK SDK
import { SDKBrowserModule } from './shared/sdk';
// END LOOPBACK SDK

// START APPLICATION MODULES
import { AppRoutingModule } from './app-routing.module';
import { AuthModule } from './auth/auth.module';
import { FeaturesModule } from './features/features.module';
import { SharedModule } from './shared/shared.module';
import { LayoutModule } from './layout/layout.module';
import { AdminModule } from './admin/admin.module';
// END APPLICATION MODULES

// START ANGULAR MATERIAL MODULES
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
// END ANGULAR MATERIAL MODULES

import { PdfViewerModule } from 'ng2-pdf-viewer';
import { FacebookModule } from 'ngx-facebook';
import { DeviceDetectorModule } from 'ngx-device-detector';

// START DEV DEPENDENCIES
import { StoreDevtoolsModule } from '@ngrx/store-devtools';
// END DEV DEPENDENCIES

// IMPORT TO FIX BUG DIALOG
import { AdressDialogComponent } from './shared/components';
// END IMPORT TO FIX BUG DIALOG

export const BOOTSTRAP_EFFECTS = new InjectionToken('Bootstrap Effects');

export function bootstrapEffects(effects: Type<any>[], sources: EffectSources) {
  return () => {
    effects.forEach(effect => sources.addEffects(effect));
  };
}

export function createInstances(...instances: any[]) {
  return instances;
}

export function provideBootstrapEffects(effects: Type<any>[]) {
  return [
    effects,
    { provide: BOOTSTRAP_EFFECTS, deps: effects, useFactory: createInstances },
    {
      provide: APP_BOOTSTRAP_LISTENER,
      multi: true,
      useFactory: bootstrapEffects,
      deps: [[new Inject(BOOTSTRAP_EFFECTS)], EffectSources]
    }
  ];
}

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    AppRoutingModule,
    AuthModule,
    FeaturesModule,
    SharedModule,
    BrowserAnimationsModule,
    SDKBrowserModule.forRoot(),
    StoreModule.forRoot(core.reducers),
    StoreRouterConnectingModule,
    PdfViewerModule,
    AdminModule,
    FacebookModule.forRoot(),
    DeviceDetectorModule.forRoot(),
    // StoreDevtoolsModule.instrument({
    //   maxAge: 25 //  Retains last 25 states
    // }),
    // EffectsModule.forRoot([core.router.effects.RouterEffects])
  ],
  providers: [
    { provide: LOCALE_ID, useValue: 'fr-FR' },
    core.reducerProvider,
    provideBootstrapEffects([core.router.effects.RouterEffects])
  ],
  entryComponents: [AdressDialogComponent], // SUITE BUG D'INGECTION DU CONTENU DANS LE DIALOG
  bootstrap: [AppComponent]
})
export class AppModule { }

Version of affected browser(s),operating system(s), npm, node and ngrx:

Browser: latest Google Chrome and Firefox (Mac) "typescript": "2.4.2" @angular/cli: "1.6.7", "@angular/compiler-cli": "^5.2.9", "@angular/language-service": "^5.2.9", "ngrx-store-freeze": "^0.2.1", "rxjs": "^5.5.8", "ngrx-store-localstorage": "^0.2.4", "ngrx-store-logger": "^0.2.1", "ngx-device-detector": "^1.2.2", "ngx-facebook": "^2.4.0", "npm": "^5.8.0", "@ngrx/core": "^1.2.0", "@ngrx/effects": "^5.2.0", "@ngrx/router-store": "^5.2.0", "@ngrx/store": "^5.2.0", "@ngrx/store-devtools": "^5.2.0",

Other information:

if you need more information you can contact me with : anthony@qrea.io Thanks

nasreddineskandrani commented 6 years ago

i think it's related to the pipeable-operators https://github.com/ReactiveX/rxjs/blob/master/doc/pipeable-operators.md you should stop using this kind of import: import 'rxjs/add/operator/map'; to allow tree shaking basically use pipe. For your effects just try something like that:

import { ..., ofType } from '@ngrx/effects';
import { debounceTime, map, ... } from 'rxjs/operators';
...
this.actions.pipe(
    ofType(ActionTypes.SIGN_IN),
    debounceTime(500),
    map((action: SignInAction) => action.payload),
...

comment: import * as AllAuthActions from './auth.actions'; then you avoid to declare this huge list and you have autocomplete to access your actions AllAuthActions.AuthenticatedErrorAction...

hope this help

brandonroberts commented 6 years ago

I don't think this is an issue with ngrx itself. Please provide a reproduction of this issue with stackblitz or a repo.

alexanderwende commented 6 years ago

I experienced the same issue when compiling a project with AOT:

core.js:1598 ERROR Error: Uncaught (in promise): TypeError: Cannot read property 'pipe' of undefined TypeError: Cannot read property 'pipe' of undefined at new FileEffects (file.effects.ts:42) at _createClass (core.js:9260) at _createProviderInstance$1 (core.js:9234) at initNgModule (core.js:9170) at new NgModuleRef_ (core.js:9899) at createNgModuleRef (core.js:9888) at Object.debugCreateNgModuleRef [as createNgModuleRef] (core.js:11719) at NgModuleFactory_.push.../../node_modules/@angular/core/fesm5/core.js.NgModuleFactory_.create (core.js:12421) at MapSubscriber.project (router.js:3287) at MapSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:75) at new FileEffects (file.effects.ts:42) at _createClass (core.js:9260) at _createProviderInstance$1 (core.js:9234) at initNgModule (core.js:9170) at new NgModuleRef_ (core.js:9899) at createNgModuleRef (core.js:9888) at Object.debugCreateNgModuleRef [as createNgModuleRef] (core.js:11719) at NgModuleFactory_.push.../../node_modules/@angular/core/fesm5/core.js.NgModuleFactory_.create (core.js:12421) at MapSubscriber.project (router.js:3287) at MapSubscriber.push.../../node_modules/rxjs/_esm5/internal/operators/map.js.MapSubscriber._next (map.js:75) at resolvePromise (zone.js:814) at resolvePromise (zone.js:771) at zone.js:873 at ZoneDelegate.push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:421) at Object.onInvokeTask (core.js:4053) at ZoneDelegate.push.../../node_modules/zone.js/dist/zone.js.ZoneDelegate.invokeTask (zone.js:420) at Zone.push.../../node_modules/zone.js/dist/zone.js.Zone.runTask (zone.js:188) at drainMicroTaskQueue (zone.js:595)

The problem has nothing to do with pipeable operators, but after some digging I found this issue and I can confirm that it has to to with barrel imports. If I reference my effects class from a barrel import...

EffectsModule.forFeature([fromStore.File.FileEffects])

...it will compile, but break with the above mentioned error in the browser. If I directly import the effects class and reference it that way...

EffectsModule.forFeature([FileEffects])

...everything works as expected.

It should be mentioned that this issue occurs only when compiling with AOT option and the module in question is a lazy-loaded module.

Edit:

I got thinking, in the linked issue, I could see that the barrel re-exports sub-barrels, just like in my case, and creates intermediary module namespaces, like fromStore.File.FileEffects. So I tried what would happen, if I skip the inner namespace. So instead of:

...
import * as fromStore from './store';

@NgModule({
    imports:      [
        CommonModule,
        ...
        StoreModule.forFeature(fromStore.featureName, fromStore.reducers, { metaReducers: fromStore.metaReducers }),
        EffectsModule.forFeature([fromStore.File.FileEffects]),
        SomeRoutingModule
    ],...
})
export class SomeModule {}

... I would separately import the previously namespaced File barrel like this:

import * as fromStore from './store';
import * as fromFile from './store/file';

@NgModule({
    imports:      [
        CommonModule,
        ...
        StoreModule.forFeature(fromStore.featureName, fromStore.reducers, { metaReducers: fromStore.metaReducers }),
        EffectsModule.forFeature([fromFile.FileEffects]),
        SomeRoutingModule
    ],...
})
export class SomeModule {}

This actually works with AOT. So it looks like it's only a problem, for deeper nested re-exports. Maybe it helps to show my store barrel:

import { ActionReducerMap, MetaReducer } from '@ngrx/store';
import { environment } from '../../../environments/environment';
import * as fromFile from './file';
import { State } from './state';

export * from './state'

export const reducers: ActionReducerMap<State> = {
    files: fromFile.reducer,
};

export const metaReducers: MetaReducer<State>[] = !environment.production ? [] : [];

export { fromFile as File };

Maybe the re-export is done incorrectly?

leehongfay commented 6 years ago

I am facing the same error:

Uncaught TypeError: Cannot read property 'ofType' of undefined at new l (main.e7f84b40dc7ee41a9acb.js:1) at main.e7f84b40dc7ee41a9acb.js:1 at Ii (main.e7f84b40dc7ee41a9acb.js:1) at main.e7f84b40dc7ee41a9acb.js:1 at new l (main.e7f84b40dc7ee41a9acb.js:1) at Object.eu [as createNgModuleRef] (main.e7f84b40dc7ee41a9acb.js:1) at n.create (main.e7f84b40dc7ee41a9acb.js:1) at main.e7f84b40dc7ee41a9acb.js:1 at t.invoke (polyfills.7da7c717970c702965aa.js:1) at Object.onInvoke (main.e7f84b40dc7ee41a9acb.js:1)

does anyone has any clue?

digitalhurricane-io commented 4 years ago

alexanderwende solution worked for me