angular-fullstack / generator-angular-fullstack

Yeoman generator for an Angular app with an Express server
https://awk34.gitbook.io/generator-angular-fullstack
6.12k stars 1.24k forks source link

Getting Vendor libraries in webpack as services in Angular #2605

Closed aabm00 closed 7 years ago

aabm00 commented 7 years ago
Item Version
generator-angular-fullstack 4.2.2
Node 6.9.2
npm 3.10.9
Operating System OS X 10 / Windows 7
Item Answer
Transpiler TypeScript
Markup HTML
CSS CSS
Router ui-router
Client Tests Jasmine
DB MongoDB
Auth Y

socket.io

Hi

I am trying to load the libraries d3.js, moment.js and Rx.js as config.entry -->vendor in the webpack.make.js and once loaded create a service with every one, but I have tried everything and nothing. I am totally stuck. I'd appreciate so much any help.

The steps i have followed are:

In webpack.make.js

if(TEST) {
    config.entry = {};
} else {
    config.entry = {
        app: './client/app/app.ts',
        polyfills: './client/polyfills.ts',
        vendor: [
            'angular',
            'angular-animate',
             ...
            'd3',  // Return d3.js, see package.json "main": "d3.js",,
            'moment',   // Return moment.js, see package.json "main": "./moment.js",
            'rxjs'  // Return Rx.js, see package.json "main": "Rx.js",
        ]
    };
}

I create a service for every library, for example for d3.js is

d3.service.ts

'use strict';
const angular = require('angular');
const d3 = require('d3');

/*@ngInject*/
export function d3Service() {
  // Service logic
  // ...

  /* We could declare locals or other D3.js
  specific configurations here. */
 return d3;
}

export default angular.module('TGMApp.d3', [])
  .factory('d3', d3Service)
 .name;

Then in the app.ts I import the 3 services

app.ts

'use strict';
 const angular = require('angular');
 // import ngAnimate from 'angular-animate';
 const ngCookies = require('angular-cookies');

// ADDED
import 'bootstrap/dist/js/bootstrap.min.js';
import 'angular-bootstrap-datetimepicker';
....
import '../components/d3/d3.service';
import '../components/moment/moment.service';
import '../components/rxjs/rxjs.service';
import test from './test/test.component';
// ADDED- END

angular.module('TGMApp', [
  ngCookies,
   ....
  test

Then I want in the test component to use the created services, in this case for example the d3 service

test.component

'use strict';
const angular = require('angular');
const uiRouter = require('angular-ui-router');
import routes from './test.routes';

export class TestComponent {
  message;
  d3;

  /*@ngInject*/
  constructor(d3) {
    this.message = 'Hello';
    this.d3 = d3;

 };
}

export default angular.module('TGMApp.test', [uiRouter])
  .config(routes)
  .component('test', {
    template: require('./test.html'),
    controller: TestComponent,
   controllerAs: 'testCtrl'
 })
  .name;

But I get the next error

Error: [$injector:unpr] Unknown provider: d3Provider <- d3
http://errors.angularjs.org/1.6.4/$injector/unpr?p0=d3Provider%20%3C-%20d3
at vendor.bundle.js:193
at vendor.bundle.js:4916
at Object.getService [as get] (vendor.bundle.js:5071)
at vendor.bundle.js:4921
at getService (vendor.bundle.js:5071)
at injectionArgs (vendor.bundle.js:5096)
at Object.invoke (vendor.bundle.js:5122)
at $controllerInit (vendor.bundle.js:10993)
at nodeLinkFn (vendor.bundle.js:9873)
at compositeLinkFn (vendor.bundle.js:9182)
at publicLinkFn (vendor.bundle.js:9047)
at Object.<anonymous> (vendor.bundle.js:39738)
at vendor.bundle.js:1473
at invokeLinkFn (vendor.bundle.js:10553)
at nodeLinkFn (vendor.bundle.js:9942)
at compositeLinkFn (vendor.bundle.js:9182)
at publicLinkFn (vendor.bundle.js:9047)
at lazyCompilation (vendor.bundle.js:9438)
at updateView (vendor.bundle.js:39656)
at vendor.bundle.js:39605
at Scope.$broadcast (vendor.bundle.js:18614)
at vendor.bundle.js:38969
at processQueue (vendor.bundle.js:16959)
at vendor.bundle.js:17003
at Scope.$digest (vendor.bundle.js:18098)
at Scope.$apply (vendor.bundle.js:18396)
at bootstrapApply (vendor.bundle.js:2044)
at Object.invoke (vendor.bundle.js:5130)
at doBootstrap (vendor.bundle.js:2042)
at Object.bootstrap (vendor.bundle.js:2062)
at app.bundle.js:81
at HTMLDocument.trigger (vendor.bundle.js:3558) "<div ui-view="" class="ng-scope">"

I have tried to load them with the plugin webpack.ProvidePlugin as we did with jQuery see this post but neither.

Thanks !!!

Awk34 commented 7 years ago

You need to import the name of the exported Angular module and add it to your dependency list:

import d3service from '.../d3.service';

export class TestComponent {
  message;
  d3;

  /*@ngInject*/
  constructor(d3) {
    this.message = 'Hello';
    this.d3 = d3;
  }
}

export default angular.module('MyApp', [d3service]) // <--- here
  .config(routes)
  .component('test', {
    template: require('./test.html'),
    controller: TestComponent,
    controllerAs: 'testCtrl'
  })
  .name;
Awk34 commented 7 years ago

Since the d3service imported would be the name of the Angular module exported here:

export default angular.module('TGMApp.d3', [])
  .factory('d3', d3Service)
  .name;
aabm00 commented 7 years ago

Thanks for you answer.

Making your changes It works!!!!, but trying things just before I saw your answer I found another way that it works too, and it's more inline with the version 3.7.6 of the generator, but now I would like to know if my version have some negatives implications that I don't know. So I am going to expose my version, exposing only the differences.

d3.service.ts

When creating a factory service with the generator it creates the default export of the module but also and export of the Service (in this case the d3Service), so I delete this export

'use strict';
const angular = require('angular');
const d3 = require('d3');

/*@ngInject*/
function d3Service() {
  // Service logic
  // ...

  /* We could declare locals or other D3.js
  specific configurations here. */
 return d3;
}

export default angular.module('TGMApp.d3', [])
  .factory('d3', d3Service)
  .name;

app.ts

I have added the services (moment, Rx and d3) as dependency in the module. With your version isn't necessary

'use strict';
 const angular = require('angular');
 // import ngAnimate from 'angular-animate';
 const ngCookies = require('angular-cookies');

// ADDED
import 'bootstrap/dist/js/bootstrap.min.js';
import 'angular-bootstrap-datetimepicker';
....
import '../components/d3/d3.service';
import '../components/moment/moment.service';
import '../components/rxjs/rxjs.service';
import test from './test/test.component';

// ADDED- END

angular.module('TGMApp', [
  ngCookies,
   ....
  moment,
  Rx,
  d3,
  test

test.component

Because I didn't export the d3Service function in d3.service.ts I dont have to import it here and either add it to the dependency list of the exported Angular module

'use strict';
const angular = require('angular');
const uiRouter = require('angular-ui-router');
import routes from './test.routes';

export class TestComponent {
  message;
  d3;

  /*@ngInject*/
  constructor(d3) {
    this.message = 'Hello';
    this.d3 = d3;
 };
}

export default angular.module('TGMApp.test', [uiRouter])
  .config(routes)
  .component('test', {
    template: require('./test.html'),
    controller: TestComponent,
   controllerAs: 'testCtrl'
 })
  .name;

In this way it's easiest but now I am wondering if I am missing something and in this new version of the generator it's necessary to export the Service (in this case the d3Service) for some reason I don't know.

Thanks

tharrington commented 7 years ago

@aabm00 why do you have to make a service? I just want to use moment in a controller... Should be relatively simple:

  1. npm install moment --save

  2. Add moment to webpack.make.js:

    config.entry = {
            app: './client/app/app.js',
            polyfills: './client/polyfills.js',
            vendor: [
               ...
                'moment'
  3. Inject moment into app.js

angular.module('myApp', [... require('moment')])

  1. Use it in a component

'use strict'; const angular = require('angular');

const uiRouter = require('angular-ui-router'); const moment = require('moment');

import routes from './dashboard.routes';

export class DashboardComponent { current_time = moment().format();

/@ngInject/ constructor() { this.message = 'Hello'; } }

export default angular.module('myApp.dashboard', [uiRouter, moment]) .config(routes) .component('dashboard', { template: require('./dashboard.html'), controller: DashboardComponent }) .name;



Results in the error:
TypeError: moment is not a function

What am I missing?
Awk34 commented 7 years ago

@tharrington moment is not an angular library, so you can't add it to Angular's dependency injector. All you need is this at the top of your file:

const moment = require('moment');

or

import moment from 'moment';
aabm00 commented 7 years ago

Hi @tharrington.

As @Awk34 comment moment isn't an angular library so to add it to Angular's dependency you need to make a service with the it as explained @Awk34 in this post.

Awk34 commented 7 years ago

Yes, but it isn't necessary to include it as an Angular dependency.