Search code examples
javascriptangularjsangular-filtersangular5multi-device-hybrid-apps

Custom filter not working in Angular Hybrid app


I am attempting to convert an AngularJS 1.6 app in to a hybrid app along with Angular 5. I have the following simple filter defined:

(function () {
    "use strict";
    var filterId = "colorPicker";

    angular
        .module('app')
        .filter('colorPicker', colorPicker);

    function colorPicker() {
        return function (input) {
            var colorCode = '#2980b9';
            switch (input) {
                case 1:
                    colorCode = '#16a085';
                    break;
                case 2:
                    colorCode = '#a38761';
                    break;
                case 3:
                    colorCode = '#8e44ad';
                    break;
                case 4:
                    colorCode = '#ffa800';
                    break;
                case 5:
                    colorCode = '#d95459';
                    break;
                case 6:
                    colorCode = '#8eb021';
                    break;
                default:
            }
            return colorCode;
        };
    }
})();

The filter is used like this: ng-attr-style="background-color: {{ $index | colorPicker }}"

This works in the AngularJS app, but I get the following error in the Hybrid app: angular.js:14525 Error: [$injector:unpr] Unknown provider: colorPickerFilterProvider <- colorPickerFilter

The filter is called from the AngularJS code as it has before. In fact, I barely have any Angular 5 code. I'm just trying to get the existing code to run as it has been, but within the Hybrid app. Shouldn't I be able to use filters as before?

Update

I think this might be related to other errors I'm getting about controllers:

[$controller:ctrlreg] The controller with the name 'myController' is not registered.

I can see the script files downloading successfully and no errors are being thrown when my app.module.ts bootstraps AngularJS. In fact, I'm somewhat certain that AngularJS is running since it was getting an error about globalVars not being injected (this was being defined in a razor view that I am no longer using) but the error went away after I recreated it in TypeScript and "downgraded" it so AngularJS could use it.

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { FormsModule } from '@angular/forms';
import { downgradeInjectable, UpgradeModule } from '@angular/upgrade/static';

import { AppComponent } from './app.component';
import { GlobalVarsService } from './core/global-vars.service';

declare var angular: any;

angular.module('app', []).factory('globalVars', downgradeInjectable(GlobalVarsService));

@NgModule({
  declarations: [
    AppComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    UpgradeModule
  ],
  providers: [
    GlobalVarsService
  ],
  bootstrap: [AppComponent]
})
export class AppModule {
  constructor(private upgrade: UpgradeModule) {
    this.upgrade.bootstrap(document.body, ['app'], { strictDi: true });
  }
}

So the files are downloading and executing, but the controllers and filter (and maybe other items) are not found by the app. I put all the old code in a folder named "old" and then updated .angular-cli.json to copy these files to the output upon build so that I can reference them via <script> tags in the index.html. This should work, shouldn't it? Or do the files need to be bundled with the Angular 5 files for some reason?

// .angular-cli.json section
  "assets": [
    "assets",
    { "glob": "**/*", "input": "../old/app/", "output": "./app/" },
    { "glob": "**/*", "input": "../old/Content/", "output": "./Content/" },
    { "glob": "**/*", "input": "../old/Scripts/", "output": "./Scripts/" },
    "favicon.ico"
  ], 

Solution

  • Found the problem. Actually I think there were two problems. One was that I think I was redefining "app" by including square brackets when downgrading my globalVars service.

    angular.module('app', []).factory('globalVars', downgradeInjectable(GlobalVarsService));

    instead of

    angular.module('app').factory('globalVars', downgradeInjectable(GlobalVarsService));

    The other issue I think was a chicken-and-the-egg kind of problem. My globalVars was being injected into my AngularJS app's config function, but I think the app might have been needed to downgrade globalVars - still not completely sure. Luckily for me, nothing was referencing globalVars in my app.js so I was able to remove the reference.

    This is the version of my app.module.ts that finally got it working. I hope this helps someone else!

    import { NgModule, APP_INITIALIZER } from '@angular/core';
    import { BrowserModule } from '@angular/platform-browser';
    import { HttpClientModule } from '@angular/common/http';
    import { HttpClient } from '@angular/common/http';
    import { downgradeInjectable, UpgradeModule } from '@angular/upgrade/static';
    import { environment } from '../environments/environment';
    
    import { AppComponent } from './app.component';
    import { GlobalVarsService } from './core/global-vars.service';
    
    declare var angular: any;
    
    @NgModule({
      declarations: [
        AppComponent
      ],
      imports: [
        BrowserModule,
        HttpClientModule,
        UpgradeModule
      ],
      providers: [
        {
          provide: APP_INITIALIZER,
          useFactory: OnAppInit,
          multi: true,
          deps: [GlobalVarsService, HttpClient]
        },
        GlobalVarsService
      ]
    })
    export class AppModule {
      constructor(private upgrade: UpgradeModule, private http: HttpClient) { }
      ngDoBootstrap() {
        angular.module('app').factory('globalVars', downgradeInjectable(GlobalVarsService));
        this.upgrade.bootstrap(document.body, ['app'], { strictDi: true });
      }
    }
    
    export function OnAppInit(globalVars: GlobalVarsService, http: HttpClient) {
      return (): Promise<any> => {
        return new Promise((resolve, reject) => {
          // Fetch data from the server before initializing the app.
          http.get(environment.apiBase + '/api/meta/data').subscribe(x => {
            globalVars.MetaData = x;
            globalVars.VersionNumber = globalVars.MetaData.versionNumber;
            globalVars.IsDebugBuild = globalVars.MetaData.isDebugBuild;
            globalVars.AuthorizedFeatures = globalVars.MetaData.authorizedFeatures;
            globalVars.User = globalVars.MetaData.user;
            resolve();
          });
        });
      };
    }