Angular: NgModule on Large Projects

Created on 7 Aug 2016  ·  144Comments  ·  Source: angular/angular

I'm submitting a ... (check one with "x")

[X] feature request / proposal

I've been reading about NgModule and I want to lay out some use cases that I'm not entirely sure the current proposal (https://docs.google.com/document/d/1isijHlib4fnukj-UxX5X1eWdUar6UkiGKPDFlOuNy1U/pub) takes into consideration.

Context

I'm part of a team building an Enterprise Framework (based on Angular 2). This framework will then be the base for other apps within the same ecosystem.

We have divided the framework into smaller projects/modules (think of them as separated npm packages). These modules are sets of controls (that are then reused across other modules) or pages that use those controls.

Example

A quick example can be:

Controls Module

import {Component} from "@angular/core";

@Component({
   selector: "my-combobox",
   ...
})
export class MyComboBox{

}

Checklist Module
// Checklist module depends on Controls module. Controls is treated as a 3rd party module.

import {Component} from "@angular/core";
import {MyComboBox} from "controlsmodule/components/mycombobox";
// Please note that we are only loading a specific component within the module, not all components inside that module.

@Component({
     selector: "my-checklist-page",
     directives: [MyComboBox, ...],
     ...
})
export class ChecklistPage{

}

Bootstrap doesn't know both Controls and Checklist modules. They are lazy loaded depending user's interaction. In this case, if the user navigates to a Checklist, the ChecklistPage component will be loaded and then the MyComboBox will also follow (because of the _import_ made by ChecklistPage)

Checklist module has other dozen components. Each depending on other dozen components from multiple modules.

It's impractical (not to say nearly impossible) to have all components imported into the NgModule declaration. We are talking of several hundreds of components that _might_ be used during app run time.

Also, the application needs to be modular and lazy load when possible. Different interactions within the app will lead to completely different modules being loaded.

Expected/desired behavior

The current solution, with component scoped directives, works like a charm for this use case. Not sure how this will play out with NgModule.

More than that, currently we can clearly see the dependencies needed by each component (in this case ChecklistPage). Making maintenance that much more easy.

Having all needed components imported into a NgModule, and then using them indistinctly on several components seems a fantastic solution for small applications. I feel that on a long term development, with several iterations across multiple years, with team rotation, ... having each component explicitly state on what it depends without looking into the template (and having compilation errors when something is missing) is a great advantage.

Conclusion

Please let me know if I was clear in my explanation. The goal of this issue is to raise awareness about this situation and get feedback from you on how to proceed.

We are available to show you our current work, we have several hundred Angular 2 components across 8 projects developed in the last year (since alpha 27).

Most helpful comment

Thanks for the feedback everyone, apologies its taken this long to get a reply - we've been very busy over the last week (migrating internal Google apps over to NgModules, so we feel the refactoring pain too)

Let me see if I can clear up some of the questions and misconceptions in here.

The first thing to understand about @NgModule() (and @Component and any other Angukar decorator) is that they are purely compile time construct - they exist to allow the angular compiler to discover a dependency graph in an application.

A (simplified) version of what our decorators do looks like:

//simplified Component decorator
export function Component(componentConfig){
  return function(componentClass){
    Reflect.defineMetadata('annotations', componentConfig, componentClass);
  }
}

They don't change or modify the behavior of the decorated class in any way - they simply attach some metadata. Angular uses that metadata to construct your application and compile templates.

In JiT mode, this happens "Just in Time" - in between you calling bootstrapModule and your first component rendering, Angular's compiler retrieves the metadata attached to classes using the Reflect API:

let metadata = Reflect.getOwnMetadata('annotations', componentClass);

In AoT mode however, this works a bit differently - at build time, we _statically_ (that is, without executing your code) extract the same metadata from the source code by scanning for decorators.

This works okay when you're bootstrapping a single component, but we've heard a lot of feedback from developers who are doing more complex things - bootstrapping multiple root components, or bootstrapping different components based on auth status, etc.

So while @Component decorators gave us the ability to statically analyze a Component, we did not have the ability to reliably statically analyze an _Application_

Things that fall under the umbrella of an "application"

  • PLATFORM_DIRECTIVES/PIPES/PROVIDERS
  • things you previously added to bootstrap()
  • compiler level settings
  • multiple root components
  • server side usage.

NgModules introduce the idea of a statically analyzable set of features. What's interesting is that in AoT compile mode, we analyze your root module, and _generate_ a ModuleFactory for each Module in the application - this is the pre-compiled version of a module, that contains _only_ the factories you statically reference in templates and those you mark as "entryComponents"

Because we've already extracted the information necessary for compilation Ahead-of-Time, we can actually tree-shake the decorators out (ngc will handle this automatically for final) - and rather than bundling your application starting at your root Module, you start at your generated root Module_Factory_, which only contains the code that's actually used in your application, so you don't pay a penalty for modularity, and tools like rollup and webpack2 can work _more_ efficiently

more to follow in next reply...

All 144 comments

It doesn't appear to me that ngModules prohibits the setup you're going for, have you played with it yet? You can have several different modules and do lazy loading, etc. http://plnkr.co/edit/NAtRQJBy50R19QAl90jg?p=info

For something more similar to what you appear to be trying to do, keep an eye material2's transition: https://github.com/angular/material2/pull/950/files

Hi @qdouble,

Thank you for the quick reply.
Looking at https://github.com/jelbourn/material2/blob/ecbb4f42e0473899f6ad15d8e4ed8f262ded7a99/src/components/button-toggle/button-toggle.ts, are you saying that in order to achieve the same functionality that we have now, we need to declare a NgModule on each component? (that is what was added at the end of the file right?)

Also, it doesn't cover the maintenance issue I've mentioned on my initial statement. Having the dependencies of each component/directive declared on its decorator is, for me, a great advantage that I would like to keep.

@jpsfs if you did want to create individual scope for each and every component, then I suppose you'd have to create different ngModules for each. While this may create more code in your case, I suppose it will create less code for the vast majority of other people by scoping my module instead of per component.

As far as the second issue, you could declare the ngModule and component right next to each other, so while it'd add an extra 3 or 4 lines of code to your files, I don't think that it creates a huge issue.

I'd say the vast majority of use cases don't require scoped directives per component, but in the case that it does, ngModules still supports that for a few more lines of code.

@qdouble Thanks.

I'm not sure this is a or/or situation, I can see both scenarios working together, no need to remove the functionality we already have. If someone wants to use modules, I can see that as a great addition. Meanwhile, the framework could work without modules (as it is working today). I believe that even the offline compilation issue can be sorted out with things as they currently are.

I'm letting this open to see if someone has something to add to the matter.

@jpsfs understood, if I was in your situation, I'd definitely prefer they left both options open :)

They did write the reason for deprecating component directives in the doc you posted, as far as it creating two scopes, them thinking ngModule scope is small enough and that it's more in line with ES6 model.

A team member also mentioned before that it's generally problematic to have two different ways to do things... and in the long term, I could see the issue here....if you have some projects where people are using ngModules and other projects where there aren't, that creates more maintenance, training and compatibility issues.

Never know what direction this project will go in until it's final, so we'll see if they take what you're saying into consideration.

Even I am currently working on designing the Architecture for a huge Enterprise App.
Unlike your situation @jpsfs , I am excited with NgModules and pretty much based my app architecture around NgModule.

Each Module will have its own set of routes and component dependencies. We can never create a piece of functionality with just one component, it needs Routes, at least one Smart Component and few Dumb Components and Services to go with it. Plug this all into a Module and you are good to go.

Coming to lazy loading, instead of loading code for each component, it looks good that each NgModule code will be loaded at once, so your functionality is fully usable once downloaded.

Creating hierarchy of Modules is much simpler as well, and it provides great Plug and Play feature for free.

We ware also currently working on an application with quite a lot of components (Not hundreds but dozens). There is no architectural need for us to split this application into several (lazy-loaded) modules, but now importing all those components into the bootstrap-file and passing them to declarations somehow feels wrong and breaks encapsulation of the component. As @jpsfs said, before it was very clear which components and directives were used by another component. So I would also appreciate to have the choice:

  • If it's a pretty commonly used directive declare it at the module
  • If it's something like TaskListItem just import it in TaskList.

Maybe some core-member can tell more about the decision of deprecating the second approach. Having worked with it for several month now, it feels pretty good ;)

To echo @choeller's point, it does feel strange moving away from the ability to provide component encapsulation.

My specific concern is that now the component names/selectors leak across the whole app, whereas before you could re-use selectors for different components by including specific directives as appropriate.

Now all selectors would need to be unique per component, right? Or am I misunderstanding how this works?

I felt that the original functionality matched the similar benefits provided by the CSS shadow-DOM emulation, in that we could worry less about selector collisions etc. across large apps. That was a great advantage IMO.

My first thought about ngModule was "Oh, that's like in angular 1". As great as angular 1 already was, angular 2 is so much better in so many points. The best point for me was, that Components create some kind of dependency tree. I have a main component with a router which defines several entrypoints with it's own component. And every component know what it needs, no reason for the main component to know what any of the components at the end of the tree needs.
Now we are back at the good old times of angular 1, where we have a giant module definition.
Remeber the times your app entrypoint looked like this?

angular.module("myApp")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.controller("…")
.component("…")
.component("…")
.component("…")
.component("…")
.component("…")
.component("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.directive("…")
.service("…")
.service("…")
.service("…")
.service("…")
.service("…")
.service("…")

I thought this belongs to the past. I started to work with ng-metadata to improve old angular 1 projects and prepare for migration. I really love the fact, that it has a dependency tree and not a global "what might appear in this app" list.

This makes reusable components more difficult. I don't get how this makes things better having everything in a global/module scope, I think the ng2 team have caved to ng1 users that don't want change.

@DaSchTour @damiandennis I understand the criticism of this architecture, however, referring to it as some sort of global scope is inaccurate, the methodology they are suggesting you take is to have feature modules: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#feature-modules

@qdouble Well, so in the end it's just changing all Components to Modules. Although this is announced as change to reduce boilerplate code, it introduces a loot of need boilerplating.

While till RC4 a component vor every "page"/view of the application was enough, know I'll have to create a module, a component and routing for every view. I get the intention. But I somehow have the impression, that it's designed to make some things easier while not concerning many other points. And even with the feature module pattern I have to cut them very small to avoid dependency hell, by adding everything that might be needed because I can't see which part of my application needs which components.

In the end the modules are that small, that they have the similar repetitive dependency lists like current components.

In the end, it doesn't solve what it was designed for and only adds a lot of work. I have the feeling that there is some mismatch between design and reality here.

developers are lazy/short of time and take shortcuts. This encourages developers to take the quick route of just including everything in bootstrap. At least with components there is some requirement to include your dependencies. Yes they may also be lazy and create a single component for the whole application but that would be easier to fix as the dependencies are all in that component not mixed up between the bootstrap file and each component file within the application.

@DaSchTour if each and every single component needs it's own scope, then yes it will create more boilerplate...but I'm taking it that the ng team is of the opinion that for most people, creating a new module for every feature section is enough and a few components would be able to live in each feature space.

Now obviously, there's no one fits all solution and having component level directives may be simpler for some people. However, it appears that a lot of the comments here are implying that they want you to just create an application with one huge ngModule tree...

I think it's more productive if the criticism is based around their actual design suggestions rather than a straw man design pattern that they aren't suggesting (i.e. creating an enterprise app that's just one huge ngModule)

@qdouble the design pattern is simple. It's use a dependency tree instead of moving dependencies to a global, module scope. I guess the main point is, that reusable components now have to be modules, even if they are very small and have only very little functionality. Angular material2 is a very good example. A Button is a module, including one component. Maybe it's a general misconception of a many developers, that a module is something that contains more than just a button. And now let's think a step further. Just following the ideas from this article https://angularjs.blogspot.com/2016/08/angular-2-rc5-ngmodules-lazy-loading.html I find my self at the point, that I have a lot of modules that import a list of angular material2 modules and each of this modules consisting of a single component.
In fakt this is exactly what angular material2 does.

Nobody really get's the point, why we now have to wrap "all" of our components into modules. Or maybe we have to see this as a split of declaration. Component dependencies are now a module and component definition are as they were.

I guess the point is, that ngModules are not just a nice addition to make things easier but we are forced to change everything. Maybe somebody should make a clear explanation why not both can coexist.

@DaSchTour well yes, I agree that if every single component you create needs it's own module, then using ngModules creates more boilerplate... I simply take it that the team doesn't think that most people will need that level of separation for every component.

I find my self at the point, that I have a lot of modules that import a list of angular material2 modules and each of this modules consisting of a single component.

You'd use shared modules for this: https://angular.io/docs/ts/latest/guide/ngmodule.html#!#shared-module

Now, I'm not really sure either why they think that it's necessary to totally remove the ability to have a component scope, but for me, some of the criticism is making using ngModules more difficult than it actually is or making the design seem more sloppy than it actually is.

I think the criticism of removing component scoped directives/pipes to be perfectly valid. However, I don't think it's necessary to make it seem like there aren't good design patterns for ngModules.

@qdouble I think nobody doubts that there are good design patterns for ngModules. But from what I understand modules are just a wrapper around a set of components, directives and services to expose them are a unit to the application. That's valid and a great idea. But why do I have to define the dependencies (that may only exist inside the module) for the module and not for the component.

Taking the example of @choeller
The module TaskDashboard has the following things

  1. Tasklist Component
  2. Taskitem Component
  3. Taskfilter Component
  4. Task Service

The Taskitem component is only needed inside the Tasklist and the Tasklist depends on Taskitem Component. The Taskfilter doesn't need the Taskitem Component. Now I don't have the Taskitem as a dependency in the Tasklist. Next step is, that I create a TaskSearch module. I add the following things.

  1. Tasklist Component
  2. Tasksearch Component
  3. Task Service

Well, I missed the Taskitem Component and it's broken. Tasklist always depends on Taskitem, but this dependency is hidden in the module. Reusing components is made more difficult and creates an additional source of errors.

Okay, while further reading through the modules guide I found this line

Components, directives and pipes must belong to exactly one module.

So the example exactly shows the concern that is raised here. There are can't be any shared component. So from my example I would have to split everything into modules.

While working on the strange and not elusive errors that come up when using ngModule I also found, that extending components now doesn't work as nice as before. I had a set of components with the needed dependencies that I could simply extend. Now I have to take care of importing the dependencies in the module I include my extended component in.

@DaSchTour in you example, Taskitem should be a part of the Tasklist Module... so that would be two components in one module logically, not one for each.

As @sirajc pointed out, a typical design pattern would be to have one top smart component, followed by additional dumb components...so in a typical real world app, most modules would consist of a few components (whether by the smart/dumb component pattern or by related feature pattern), not just one component per module unless you are just building 3rd party components or something.

@qdouble @DaSchTour It's true that the new architecture is not neccessarily meaning that you list all your components in one file, but looking at the app we are building I would currently pretty much go with this statement of @DaSchTour

I have the feeling that there is some mismatch between design and reality here.

So we have quite a bunch of components representing small units on the page like TaskListItem that are 100% bound to a special view. Creating Modules for each of those pages would be a total overkill. In our case there are very few reasons for splitting into multiple modules but way more reasons for encapsulating components.

tl;dr
It's cool to be able to define some dependencies on module level, but it's really sad, that we are not able to define dependencies on a component level any longer

@DaSchTour, In material2 button component is tied to module to make it standalone. Those who needs to use button can import ButtonsModule. Also if you see the Module code, it contains two compoents MdButtonand MdAnchor, wrapping then in ButtonsModule makes things easier to use

I'm wondering if its possible to create some hybrid component/module object that merges the two concepts into one to prevent duplication of files. Essentially being able to declare a component as a module once, and then import it as a module, as needed. It would honor the current approach, but minimize boilerplate.

@choeller I agree that having modules is a good addition, removing dependencies at the component level however just seems wrong.

Piling on to what others wrote above, I think the core idea here is not to experience the misery of a huge project with a huge module with hundreds of components in it. Rather, it is to build an application out of a reasonable number of medium-sized NgModules. Big enough that they are not trivial, small enough that you don't have a huge number of them; divided along fault lines that would tend to facilitate reuse and modularity in the old-fashioned computer science sense of high cohesion and low coupling.

By doing so, modules should turn out to be quite a useful concept to have them play on large projects.

Thats very well summarized @kylecordes. NgModules helps in nice composition of Application. Small reusable modules makes up whole application.
Other benfit includes ambient availability of directives. Earlier we used to add ROUTER_DIRECTIVES in whole application. Now RouterModule.forRoot() does that for us.
BrowserModule, CommonModule, FormsModule makes more sense than including directives through each component.

MaterialModule on the other provides everything and if you need finer control then ButtonsModule etc will help us.
That's the beauty of composition. Embrace it and create your Symphony

On top of this is LazyLoading. If not for NgModules how can one define the no of components and services that needs to go together to create one Routable unit. You cant download loose files as this will lead to huge no of network requests. Else you need to create a bundler where you list all dependent files ro create one bundle. NgModule does this with intuitive syntax.

@kylecordes

Piling on to what others wrote above, I think the core idea here is not to experience the misery of a huge project with a huge module with hundreds of components in it. Rather, it is to build an application out of a reasonable number of medium-sized NgModules. Big enough that they are not trivial, small enough that you don't have a huge number of them; divided along fault lines that would tend to facilitate reuse and modularity in the old-fashioned computer science sense of high cohesion and low coupling.

Wouldn't those fault lines be very different for application developers vs library developers. Library developers should be in the minority, but that seems to be the main complaint. When building a reusable framework, the goal of wanting to minimize the module size leads to a lot of extra noise.

@sirajc

On top of this is LazyLoading. If not for NgModules how can one define the no of components and services that needs to go together to create one Routable unit. You cant download loose files as this will lead to huge no of network requests. Else you need to create a bundler where you list all dependent files ro create one bundle. NgModule does this with intuitive syntax.

I was able to easily do this with the old router simply using a component that functioned as a container for a section of my application. This module brought in only the direct components it needed, which would bring in their own dependencies. Any decent module loader would be able to load the dependency chain (via imports) of that component without including every sub-dependency in the top-level component. The same goes for bundlers which should use some sort of module resolution logic. In short: there's no reason, from a lazy-loading perspective, why a developer should need to declare all contained dependencies in top-level component.

It really feels like ngModule is a solution in search of a problem...

There's something to be said for being able to open up any given component, glance at it's directives array, and know exactly what components/directives it depends upon. Is it more verbose having to import a button component throughout the numerous components that use it? Yes, but I'd rather have some extra boilerplate and have things be explicit and immediately scannable than searching up the component/module tree to see how the heck component Y is using component X in it's template without ever importing it.

The argument being made above is that should someone desire to keep components isolated from each other, they could arguably create a module for each component -- but then at that point you're writing even more boilerplate than the original component ever had.

There are obviously other benefits to ngModule I haven't covered, and while there are benefits to being able to bundle sections of an application into feature modules, it really feels like a step backwards when it comes to clarity and having components being encapsulated pieces of code that don't leak scoping all over the place.

Part of the "hard sell" that the ng2 team had to make to the community from ng1 was "yeah, you're going to have to import and declare your directives for each component, but trust us, you'll appreciate being more explicit as your application grows". I am a big believer in suffering through a bit of repetition for the sake of readability and scannability in a large-scale team environment where a team member may only be working on a very small subset of components at any given time.

At the very least, I don't see any reason why ngModule and the directives/pipes property can't co-exist. The alternative is that every single component in every single application written for ng2, up to RC5 is now using deprecated code that needs to be non-trivially refactored. These really aren't the kind of changes that I'd expect this late into development...it's pretty disconcerting honestly.

ngModules does essentially require a rewrite of most applications if they are to be structured properly... but the biggest misconception is that they are forcing a particular scope level when the reality is your modules can be as big or as small as you want them to be.

If you absolutely need to create a new module for every single component it results in more boilerplate = valid.

Most application should require you to create a module for every single component = invalid (especially if you are using smart/dumb component patterns and feature patterns)

For the first time today, I felt the client side / JavaScript fatigue that a number of colleagues have conveyed to me. I have lost countless hours over the last few days trying to refactor our RC4 application to use the new RC5 modules.

I definitely agree with a number of sentiments in this thread, particularly about giving developers a choice in terms of the way they wish to structure their component dependencies. I dislike that a larger ngModule dilutes the clear dependency graph between components and that smaller ngModules add additional boilerplate code to each component. When analysing a module, all I know is that at least one of the referenced components needs stuff from the module's imports or sibling declarations. If I break a module apart into multiple other modules, I'm basically left with trial and error in determining which dependencies the new modules require (careful inspection of the component template helps, somewhat).

Finally, if I "forget" to export components, my component simply doesn't render, no errors given. Its easy to find if you have a very flat module graph, near impossible when you have a number of levels!

At this stage I'm dejected, disappointed and disheartened. I've gotten a team of developers and business stakeholders on board with a technology I now don't know if I can take forward. I really hope a better balance can be found.

At this stage I'm dejected, disappointed and disheartened. I've gotten a team of developers and business stakeholders on board with a technology I now don't know if I can take forward. I really hope a better balance can be found.

I guess the point here is, that there is a giant number of developers waiting for this technology to be ready for production and although it's called Release Candidate every new candidate brings breaking changes and it feels more like an alpha.

I'm sure that a lot of teams have spend hours on architecture for their new apps and now it's all nuts.

What will be the next breaking change? I can't even imagine, but I fear that somebody will find a way to introduce the next breaking change.

From the RC5 blog post:

If you’ve written any Angular 2 code at all though, you’ve probably asked yourself “but WHY do I have to list all these things!?” - especially if you’ve noticed that certain directives and pipes in Angular 2 are “special” - they’re available to your entire application without you doing anything ( *ngFor / *ngIf / *ngSwitch, for example).

I personally haven't seen that question asked by anyone in quite some time. Up until RC5, the Angular team, online learning resources, books, etc. have all made it pretty clear why those declarations are needed -- and it seems like everyone accepted (and some embraced) that fact a long time ago.

As for the questions about why some are "special" and don't need declared, I don't think anyone would argue against there being a list of critical, "low-level" directives that are so ubiquitous to warrant being "blessed" as globally-available by the platform holder (Angular team).

If the code is already present to handle the hoisting of the directives up into a single ngModule, and that code works invisible to the end-user, what is the harm in allowing both approaches? That is a genuine question, as I may not be aware of some of the intricacies around what might happen if a developer mix-and-matches the approach. There are plenty of other "split choices" in Angular 2 -- model vs template-driven forms, 3 different language choices, input/output/host decorators vs property approach -- what is the harm in another at this point?

I could go on, but the point I'm trying to make is that the Angular team and community has invested quite a bit of time to hammer home the message that being more explicit is a _good_ thing, only to swoop in -- at _release candidate 5_ of all times -- to present us with a solution that hasn't been a problem for quite some time.

Anyone from the team feel like chiming in? I don't want this to become an echo chamber -- I'd love to hear the other side of the argument. It just seems like something so large came out of the blue without consideration for things like tooling, training, or how close to final release we seemingly are.

This discussion raised a very big problem for many developers who wanted to start developing early with the highly expected angular 2 : breaking changes. I did a bunch a proof of concept based on angular 2 since beta 17 and made important companies and organization adopt it. I don't regret but I'm not sure I did well either. Some of the most recent project was a POC and the battle was against Vue.js. Angular 2 clearly won that fight hands down. But today, with all the code rewriting, breaking changes, not-really-RC release, people starts chasing me and it gets pretty serious. It wouldn't have been the case if I offered a Vue or React development and that's very frustrating because of the discredit Angular 2 can put people in.

It very seems to me that I don't share the same definition of Release Candidate as the Angular Team.

As for the topic, the NgModule, I totally co-sign @jpsfs, if you have to list all your the components and micro-components within your module declaration, you better have a prune function somewhere or to be the king of modelization because all powerful it may be, it's too sensitive for large scale projects...

I think this additional higher level of modularity was nearly inevitable; and that various competing frameworks will eventually end up with something analogous.

I wonder though, if it was really necessary to make this new coarse-grained module system completely orthogonal to the underlying module system, or whether it may have been possible to make each top level directory, and all the Es6 modules contained within it, implicitly comprise a coarse-grained NgModule. Perhaps such a convention driven mechanism could be added on top of the current mechanism, and therefore remove the NgModule boilerplate for projects willing to follow such a convention.

(Of course I also share the frustration with others here, of such significant changes in the "RC" stage... I'm sure the core team though, also, if they had it all to do over, would have attacked high level modularity, and the forces that drive it (lazy loading, pre-compilation) much closer to the beginning of the project instead of at the release candidate stage. Such is life though, it is always easy to look backward and think "well if we had known then what we know now...")

If compilation support is the primary reason behind appearance of the ngModule, it definitely should not replace the existing component based scope for directives and pipes.
On the other hand, basing on route configuration and dependencies the angular compiler could theoretically itself split the application code to the appropriate compilation units (modules).

Personally I feel this change is bringing a low of boilerplate without much advantage.

I could argue that instead of using modules we could have used components as units of the application. This is probably what people were doing before rc-5 anyway.

If the modules are needed for internal purposes of this framework (like lazy loading and related things) then they should be hidden from the developer somehow so they don't pollute the code and make for a worse coding experience. And I'm not even talking about maintainability...

The first time I saw angular2 I thought: 'Hey, they are going in the right direction with this, seems like they learned something from angular 1'. You ditched the internal modules from angular 1 and instead used ES modules, other changes were also welcomed (components, typescript and so on)

But the more I wait for this to get stable the more it makes me cringe because of breaking changes, lack of documentation and poor error messages. Now you come with this NgModules and basically undo at least one great thing you changes since angular 1.

Even worse, if I have an app which started with rc-2 or 3 (which were theoretically almost stable) I now have to do a lot of work to create a good angular2 rc-5 app and need to somehow explain the customer and other developers this. And even if I do so, God knows what you'll change next and my work might have been in vane.

I know angular 2 has been early stage for a while now, but we are RC-5, people have pretty solid projects using Angular 2 by now and you are still doing changes not only breaking from the API point of view but breaking as a way of thinking in Angular.

Its not good ngFor and some imports change or that injecting components dynamically changes 3 times in 5 releases but this I can accept and understand. Bringing changes that break not only apps but the way developers think in this framework so late in the developing cycle I cannot accept unfortunately.

Anyway, as you can see, a lot of people agree with what I have to say and some are even angrier. You have a huge framework which people trust (because Google mostly) but you can't bet everything on it or you might have a surprise since everyone has choices (and when talking about JS, there are A LOT).

If the new Module would have a template it could be the Component. Think about it.

The next step would be to move all variables and functions from Components into the the Module class. Seriously. Then we had a huge central library of _all possible_ actions within our shiny module. This would be awesome! I don't know why you stopped at the centralization of declarations and dependencies. There are many more decentralized, structured code lines there!

Can anyone from the core team have a say about all this? From the notes of the last weekly meeting, the team is going ahead with the removal of the deprecated APIs.

@mhevery

This whole hoisting thing broke my application really subtly when I upgraded today.

  • I had to change a component's selector from class to element, because in an entirely unrelated component, that class is used for styling and suddenly started to instantiate the component.
  • I'm pretty sure #10850 only exists, because I had two components called SpectrumComponent (one entire page to navigate to without a selector and one "shared" component used in multiple components for visualization with selector). Hoisting likely gets confused by that - directives: declarations don't.

For my SVG components, this now becomes a larger issue, because I cannot use a custom element there. I don't yet know how to scope these to make sure their class selectors don't trigger a component instantiation anywhere (!) else in the application.

It seems to be en vogue these days to ignore the meaning of "Beta" and "Release Candidate" (ASP.NET Core being another high-profile example) in large projects. But being faced with breaking changes that have the potential to silently break an application literally anywhere is more frustrating than it should be. I really hope the promises coming with those changes bear fruit soon.

ngModules makes sense. Removing the ability to declare/provide at the component level doesn't.

Small example

I have a application that contains a number of different dialogs (some simple ones - let's call them simple-1 and simple-2, and one complex one that contains other components - let's call that complex 3).

At the moment I have a simple folder structure (obviously the app sits out of this)

- dialogs
   - simple-1 (containing single component and template for simple-1 dialog)
   - simple-2 (containing single component and template for simple-2 dialog)
   - complex-3 (contains the main complex-3 component plus a number of other internal components)
      - internal-component-1
      - internal-component-2
      - internal-service-3

To me, this is a perfect feature set to put in an isolated feature module. So I add

   dialogs.module.ts
   - simple-1
   - simple-2
   - complex-3
      - internal-component-1
      - internal-component-2
      - internal-service-3

And I add Simple1Component, Simple2Component and Complex3Component as declarations to DialogsModule. Perfect, works well and makes complete sense.

But...

What about all those internal components in complex-3. I now have two options

  • Add all the external complex components and services to DialogsModule. This is a poor decision because it breaks encapsulation of complex-3 because now everything in dialogs module knows about it's internals (internal-component-1, internal-component-2, internal-service-3)
  • Make complex-3 a module in it's own right. This fixes the scoping issue (and I believe I can export modules from another module so the client only needs to import the wrapper module) but now leaves me with a mix of components and modules for similar groups (dialogs).

Neither of these really makes sense, and as the application scales, the conflict between the two will only increase.

The only sensible/obvious/maintainable solution to me would be to use the module to export the top level components (simple-1, simple-2, complex-3) but let the complex-3 _component_ define it's own internal components itself.

/cc: @robwormald

I've been thinking about what @kylecordes said earlier about using the existing Typescript module system to define courser grain collections of code, rather than adding the new ngModules thing. I would like to submit an example for consideration, that uses Typescript modules to define ngModules in a 1-to-1 fashion. Comments and thoughts are welcome.

https://github.com/jbalbes/autoNgModule

10901

I might have missed something, but the way dependency injection works with non-lazy-loaded modules might become a large issue for "Large Projects" (mine isn't and it's already suffering).

this is interesting. we already have a enterprise application on Angular1. and we are using requirejs to define package(s) for various modules (each module contains 10-20 directives/services/filters). we bundle these packages into single file for production (except main directives). directives are lazy loaded into application when they are used (we have a core directive that lazy loads the required directives).

our application routing page templates are housed in cms application, and cms users can drag/drop main directive based UI widgets between different page templates (this is why we needed a lazy load support for directive to reduce the main script file size on browser load as most of our application is based on directive based widgets)

Angular1 allows to lazy load directives to a module by getting reference to register providers.
is this type of lazy loading components to a module and compile them to dom possible in Angular2 ?

I agree with what others have suggested that ngModules is a solution looking for a problem. The potential benefit of LazyLoading in my mind does not justify the forced deprecation of component directives/pipes dependencies. Maybe I'm wrong here, but I feel like the majority of applications won't really see any realized benefit from Lazy Loading, which to me is the only true benefit provided by ngModules at all.

Maybe ngModules is attempting (and hey, maybe it will succeed) to make the development structure/patterns nicer, but based on what little I've looked at and discussed the API thus far, it's moving into a direction that is just more complicated to follow and maintain.

Also, do we really need another module system?

No one could dispute that NgModule adds a new kind of thing we didn't have to deal with before. And I as much as anyone much appreciate the pre-RC5 simplicity, especially of very small programs, that has been lost. I also dearly miss being able to tell people that Angular 1 had its own module system, but with Angular 2 we just use the module system of the underlying language. Oops.

However, there are actually quite a lot of people, including many technical leads and other decision-makers at large companies trying to build large things, who are extremely interested in lazy loading and template pre-compilation... Both of which are substantially enabled by NgModule.

One more thing though. The part I like least about the new Angular modules is the name. The word module, it is so overloaded. I was talking about this for some other developers here yesterday, and we sort of like the word "package" instead, it sort of captures a similar idea well and it does not have a bunch of competing things with the same name, at least not in the JavaScript ecosystem.

@kylecordes issue about the name https://github.com/angular/angular/issues/10087

Wouldn't the logic presented there also disqualify using the word 'module' as well, since it conflicts with as many, if not more, concepts?

@Barryrowe couldn't of said it better myself - really seems like the majority of apps won't benefit from this - does it just end up being added complexity? I hope not.

I wonder though, if it was really necessary to make this new coarse-grained module system completely orthogonal to the underlying module system, or whether it may have been possible to make each top level directory, and all the Es6 modules contained within it, implicitly comprise a coarse-grained NgModule. Perhaps such a convention driven mechanism could be added on top of the current mechanism, and therefore remove the NgModule boilerplate for projects willing to follow such a convention.

This is a key point. ESModules work very well for structuring an application if you structure your application correctly. For example, you can easily make a deeply nested export easily available at the top level of your API by simply re-exporting it at a higher level. I am also in favor of adding more, optional, conventions to the framework in general.

The first time I saw angular2 I thought: 'Hey, they are going in the right direction with this, seems like they learned something from angular 1'. You ditched the internal modules from angular 1 and instead used ES modules, other changes were also welcomed (components, typescript and so on)

But the more I wait for this to get stable the more it makes me cringe because of breaking changes, lack of documentation and poor error messages. Now you come with this NgModules and basically undo at least one great thing you changes since angular 1.

Indeed, Angular 2 was supposed to leverage the rich emerging web technology stack that was not available when AngularJS was developed. Yet, while the justification for so many of the design decisions made for Angular 2 has been "Web Components must be supported" ironically the framework is diverging from standards in ever increasing ways. Now there are good reasons to diverge from standard, when the standards are bad.

I feel like the framework is not using its core tools to their full effect. For example, the TypeScript declarations are not exactly idiomatic and are littered with any and any[]. They also ignore powerful language features that improve the usability and toolability of APIs. They also contain abhorrent things like export declare type Type = Function.

Interestingly, it seems that a fair number of decisions have turned out to be Dart related. Dart is not something most Angular users care about. It has a very different notion of what types are and how they are used as compared to TypeScript. Dart <-> JavaScript interop is often a non-starter.

@aluanhaddad, the dart implementation was forked into a separate project recently, so hopefully some of your concerns will be remedied soon.

This change has added a significant degree of complexity, both in terms of overall LOC and mental modeling of component dependencies. My component files are slightly cleaner but at the expense of explicit and in-place dependency definition and comes with the addition of redundant feeling .module files.

Reducing boilerplate sounds like a good thing but I would argue that it can be overdone. Further, it seems that this particular boilerplate reduction effort is better described as a movement of boiler-plate as opposed to an outright reduction.

I think angular is trending towards more difficult an complex quickstarts. There's already quite a bit to deal with in terms of toolchain setup and the list of files and things that must be understood (if one is to adhere to best practices) for even the most barebone application is growing.

.@jbalbes thank you, that's good to hear.

@radusuciu you're absolutely correct, this is not a reduction in boilerplate but a transference of that boilerplate to another area of the application. As has already been said by others there is no one here conflict between component level scoping and NGModule level scoping. What we need is granular scoping that we can choose which components, directives, pipes, and services should be available to lower tears of the application based on their application-specific usage and semantics

It's small wonder to me that modules add yet more complexity of dubious real value to the app author. They were not part of the original design and seem like a late addition only added because other things hit a wall and couldn't be made to work (lazy loading, compilation etc...) without another major change being made.

I don't think all the implications to app development vs "make the other promised features work" were as high on the agenda when they were added but as more and larger apps are converted to RC5 they are starting to become clearer.

I haven't even started trying to convert my app to modules, it will probably die on RC4. I know so many parts will need to be completely re-worked and all I'd get for all that effort is to be closer to the next breaking change RC with even bigger runtime bundles because minification, surely something pretty basic and fundamental, is broken. RCs are supposed to get more stable and feel more finished as they progress, this just doesn't.

Gang, NgModule is an irritation as many have eloquently explained. But practically it is not that big of a thing. Over here (Oasis Digital / Angular Boot Camp) we have already updated numerous small things, and a few not-so-small, to RC5, and it was done in a short while (after a long while of study and understanding).

Everyone's app is going to be impacted differently. Plus I think your attitude to churn and change in Angular 2 is possibly going to be different if you are focused on delivering an app vs if you are also selling training and support for Angular 2. For me, all the work to upgrade to RC5 would be completely dead and wasted effort - it would actually deliver negative benefits (larger bundles) so is hard to justify.

Just thought I'd give my thoughts coming out the flip-side of a five day push on our platform to get to RC6* (we're on nightlies, RC6 hasn't technically dropped yet at time of writing). I feel that while my original comments are still valid, there is a sense of relief for having reached this milestone. The biggest struggles continue to be using the new routing syntax - way too many empty path: '' rules! (I digress) but that is something we'll probably abstract away.

For NgModules on the nightlies I have to say the error messages are getting better making the whole process a little easier to work with. Components and Directives are typically called out when they aren't configured correctly with enough information that you're not walking a massive NgModule dependency tree to find the problem. The main issue we hit now is just functionality missing on pages with no obvious error messages. This is typically because a component wasn't declared or imported/exported correctly. Its a little tricky but with education its easy enough to deal with. Testing is now our next push, its completely dead in the water. But like the functionality push, we'll get there in the end.

We took a deep breath, did something else for a few days and then smashed it. We're holding on tight for the next ride, whatever that is! Good luck to everyone that is affected by this in a small or large way. It would have been nice to have some sort of dialog from the Angular team on this one, The hardest problem we faced was the lack of communication. We still don't know what's on the cards for NgModules last week's meeting notes offered no insights. Lets see what happens this week!

@SaltyDH I think this is valuable feedback.
I think the complaints about communication are not really appropriate.

The implementation people build the new release and the docs people update the docs.
The docs are usually somewhat behind for obvious reasons, because they can only document after it was already coded. Writing docs also is hard and time-consuming work.

If you switch to the next release the day it was released or even using nightlies, it's a bit unfair to complain that not everything was communicated yet.
The Angular team also can't know in advance what might cause difficulties for user in all cases.

There are always members of the Angular team providing support on Gitter and issues.
You also should be aware that communication burden is quite asymmetric, with a few Angular team members to thousands of developers.

I agree completely that this is easier on the master build (almost-RC6) with the last year+ of deprecations finally going away.

@kylecordes in what way does it get easier to work with on master?

Design document for NgModules (earlier known as AppModules when design doc was written) was released more than a month ago before RC5 was released, and I was kind of prepared for it to come.
Moreover, Since NgModule was newer concept it was hard to grab it completely and migrate your medium to large App.
What I did instead is, first completely understand the nuances of NgModules and How to think in NgModules to design an Angular 2 Application. Then created a self learning app with application of NgModules.
Second step is - think of how current app can be broken in modules and then migrate them top down. creating one Module at a time starting with AppModule. This really helped and the final App looks more organized.
image

And yes, Gitter was really helpful on providing help on NgModule even before RC5 was released. Post RC5 we have docs and blogs available for us to learn much better.

@zoechi Without derailing this discussion too much I feel I must clarify my comments. I wasn't referring to documentation at all, most of my understandings have come from reviewing commits and issues here in GitHub, I'm fine with that. My frustrations come from a lack of representation here in this issue, we are at 61 comments and counting. A simple, "We're aware of everyone's concerns here in this issue, we truly believe this is the best approach for Angular 2 and we recognise the difficulties it will cause but it is better for the future of the product". Gitter is great but individual messages often get lost in a sea of people trying to understand different aspects of Angular 2. I did reach out through this medium but due to timezone differences or other priorities, I wasn't able to get the authoritative feedback I could use to make an informed decision. What did help was this Adventures in angular podcast https://devchat.tv/adv-in-angular/106-aia-angular2-rc5-and-beyond. This gave me the confidence to go forward with the NgModule upgrades.

All the best.

Thanks for the feedback everyone, apologies its taken this long to get a reply - we've been very busy over the last week (migrating internal Google apps over to NgModules, so we feel the refactoring pain too)

Let me see if I can clear up some of the questions and misconceptions in here.

The first thing to understand about @NgModule() (and @Component and any other Angukar decorator) is that they are purely compile time construct - they exist to allow the angular compiler to discover a dependency graph in an application.

A (simplified) version of what our decorators do looks like:

//simplified Component decorator
export function Component(componentConfig){
  return function(componentClass){
    Reflect.defineMetadata('annotations', componentConfig, componentClass);
  }
}

They don't change or modify the behavior of the decorated class in any way - they simply attach some metadata. Angular uses that metadata to construct your application and compile templates.

In JiT mode, this happens "Just in Time" - in between you calling bootstrapModule and your first component rendering, Angular's compiler retrieves the metadata attached to classes using the Reflect API:

let metadata = Reflect.getOwnMetadata('annotations', componentClass);

In AoT mode however, this works a bit differently - at build time, we _statically_ (that is, without executing your code) extract the same metadata from the source code by scanning for decorators.

This works okay when you're bootstrapping a single component, but we've heard a lot of feedback from developers who are doing more complex things - bootstrapping multiple root components, or bootstrapping different components based on auth status, etc.

So while @Component decorators gave us the ability to statically analyze a Component, we did not have the ability to reliably statically analyze an _Application_

Things that fall under the umbrella of an "application"

  • PLATFORM_DIRECTIVES/PIPES/PROVIDERS
  • things you previously added to bootstrap()
  • compiler level settings
  • multiple root components
  • server side usage.

NgModules introduce the idea of a statically analyzable set of features. What's interesting is that in AoT compile mode, we analyze your root module, and _generate_ a ModuleFactory for each Module in the application - this is the pre-compiled version of a module, that contains _only_ the factories you statically reference in templates and those you mark as "entryComponents"

Because we've already extracted the information necessary for compilation Ahead-of-Time, we can actually tree-shake the decorators out (ngc will handle this automatically for final) - and rather than bundling your application starting at your root Module, you start at your generated root Module_Factory_, which only contains the code that's actually used in your application, so you don't pay a penalty for modularity, and tools like rollup and webpack2 can work _more_ efficiently

more to follow in next reply...

@robwormald I can not put into an emoji the amount of appreciation for this kind of background information.

Thank you!

Another issue we ran into that NgModules solve:

Consider a case where you want to build a DialogService or similar.

Historically, you would do something like:

@Injectable()
export class MyDialogService {
  //inject the dynamic compiler 
  constructor(private componentResolver:ComponentResolver){}

  //accept a component and a viewContainerRef
  showDialog(component:Type, target:ViewContainerRef){
    //compile the component into a factory, async
    return this.componentResolver.resolveComponent(component)
      .then(componentFactory => {
         //dynamically insert the compiled componentFactory into the view:
        return target.createComponent(componentFactory);
      })
  }
}

...which you'd use like

myDialogService.showDialog(MyRandomDialogComponent).then(...)

Things to notice here -

  • component compilation in JiT mode _must_ be asynchronous, because of external templateUrls and styleUrls
  • a viewContainer accepts a _componentFactory_ - so now your code has a problem: you either have to rewrite code to switch modes (between JiT and AoT), _or_ you have to always assume the component insertion API is asynchronous. This might be okay in a case of a dialog, but if you're constructing complex UIs dynamically (think a Dashboard or a Grid View or whatever), you incur a huge amount of unnecessary Promise scheduling.
  • 3rd party or shared services/components (like SharedDialogService) have the same issue, having to accept _either_ Components or ComponentFactories

This problem crops up for _any_ application that's doing dynamic component insertion (that does not necessarily mean lazy loading) - dialogs, routers, etc, all expect to interact with ComponentTypes (the classes) instead of selectors (foo-bar).

So, when you add a Component to your entryComponents array in an NgModule decorator:

@NgModule({
  declarations: [ MyRandomDialogComponent ],
  entryComponents: [ MyRandomDialogComponent ]  
})
export class MyApp {}

What this tells the compiler is "generate me a mapping between MyRandomDialogComponent and its compiled MyRandomDialogComponentNgFactory", and _store it in the NgModuleFactory in which it is declared_

Thus, an NgModule driven version of the above dialogService looks like:

@Injectable()
export class MyDialogService {
  //inject the component factory resolver 
  constructor(private componentFactoryResolver:ComponentFactoryResolver){}

  //accept a component and a viewContainerRef
  showDialog(component:Type, target:ViewContainerRef){
    //*retrieve* the componentFactory by component, sync
   let componentFactory = this.componentFactoryResolver.resolveComponentFactory(component)
   //add the componentFactory to the view, sync
   return target.createComponent(componentFactory);
  }
}

Now, any Component you've added to entryModules can be synchronously retrieved and dynamically inserted into the view, and your code does _not_ have to change depending on what mode you're in, nor do 3rd party/shared libraries have to worry about compilation logic.

Thank you for the explanation @robwormald

One question is: does that necessitate the removal of directive/pipe declarations? Is there a reason why they can't co-exist with NgModule?

I feel like folks, including myself, are frustrated not at the "new kid on the block" NgModule or any misunderstanding of it, but that the "old way" of doing things, which seems to be _working well_ with many folks, is being forcibly removed, especially at this late RC stage. I think this is an important distinction from any resistance to NgModule itself, which I have not seen much of, that has not been directly addressed yet.

I also see that the pull request for removing directive/pipe declarations is up (https://github.com/angular/angular/pull/10912) - I'm hoping we can get a response on this point before anything is set in stone.

Thanks in advance. Angular has undoubtedly been a joy to work with and I appreciate very much the team's hard work for the past year+

There's a few comments in here regarding "global scope" - this misunderstands the mechanics of the compiler and modules (which is entirely understandable, this is complex stuff).

Dumping your entire application into a single NgModule is "fine" in the way that dumping your entire application's state onto $rootScope was "fine" in Angular1 (read: it works, but its poor application design)

In Angular 1, bringing a module into an application more or less "polluted" your entire application, as everything was dumped into a single injector bag.

In Angular2, NgModues have some very powerful scoping mechanisms that allow composition without pollution. Again, it goes back to the compiler.

When Angular is traversing and compiling your Application, each component it encounters is _compiled in the context in which it was declared_

For example - imagine you have a SharedModule with some functionality you want to share across an application

@Component({
  selector: 'shared-component-one',
  template: `
    <div>Shared Component One</div>
    <shared-component-two>
  `
})
export class SharedComponentOne {}

@Component({
  selector: 'shared-component-two',
  template: `
    <div>Shared Component Two</div>
  `
})
export class SharedComponentTwo {}

@NgModule({
  declarations: [ SharedComponentOne, SharedComponentTwo ],
  exports: [ SharedComponentOne ]
})
export class SharedModule {}

Both SharedComponentOne and SharedComponentTwo are _declared_ in the SharedModule - declarations imply _ownership_ - so both of these Components are owned by SharedModule. However, _only_ SharedComponentOne is _exported_ from the module - SharedComponentTwo remains _private_ to SharedModule.

If you were to bring in SharedModule and use it in another module, like so:

@Component({
  selector: 'some-component',
  template: `
    <div>hello from some component</div>
    <shared-component-one></shared-component-one>
  `
})
export class SomeComponent {}

@NgModule({
  imports: [ SharedModule ],
  declarations: [ SomeComponent ]
})
export class SomeModule {}

...when the compiler begins to compile SomeComponent, it discovers the shared-component-one selector, and because SharedModule (which exports SharedComponentOne) is imported into SomeModule, it knows that shared-component-one === SharedComponentOne.

Interestingly, when the compiler actually compiles SharedComponentOne, it does so "inside" SharedModule, which allows it to use things within SharedModule that are not exposed to the outside world.

This is very similar to how it used to work with Component.directives, and in this case they're equal.

Consider though if you had something like a Tabs feature:

@Component({
  selector: 'my-tabs',
  template: '...'
})
export class TabsComponent {}

@Component({
  selector: 'my-tab',
  template: '...'
})
export class TabComponent {}

Both of these components are top-level, there's no hierarchical relationship to rely on. These led to the explosion of a sort-of-convention of people doing

export const TAB_DIRECTIVES = [ TabsComponent, TabComponent ]

A more complicated Tabs feature might have a service that managed multiple instances of tabs, so then you've got to also handle that case...

export const TAB_PROVIDERS = [ ... ]

and using it becomes an exercise in remember all the things you have to export and import...

import {TAB_DIRECTIVES, TAB_PROVIDERS}

and then providing all of those things in the right location _everywhere_ you want to use it. Its also very easy at this point to forget to import ALL of the things you need for a feature and end up with weird silent failures, or components being compiled without all their required context.

With an NgModule, you can simply package them up into a single unit, and pass that around your application, and it import it where relevant.

For libraries like Material Design, which might have a number of features spread across multiple modules, you can leverage the same import/export semantics to make it even more portable:

@NgModule({
  exports: [ TabsModule, NavbarModule ]
})
export class MaterialSharedModule {}

pulling that single module allows all your application to use components, and again, at AoT time, only those you use will be imported into generated code.

As to why we removed Component.directives / pipes, we've heard plenty of feedback in both directions. We feel like having two ways to accomplish the same thing is generally a bad pattern, so we made the choice to enable people to generally write less code, and those who want the explicit scoping previously offered by Component.directives can accomplish the _same_ functionality by scoping at the module level.

In RC5, we did a sort-of automagic hoisting of Component.directives into the "global" scope. This was an attempt to smooth the transition, and while it worked for most people, the mixed old-and-new behavior caused some weird errors and behavior that while frustrating, are temporary. This goes away in RC6 (and error messages have been improved)

As to why this changed so late in the game - we can only apologize. We don't like making last minute changes any more than any of you do. At the end of the day, we're quite literally building a framework in a way that has never been done in front end land before, and thus we'll run into unanticipated issues and design problems. This was one of those cases, and it was strongly debated and investigated among the core team before we made the decision. Again, we're sorry for the trouble, but we're confident the net result is a much nicer way of building _applications_, which is why we're all here.

Thanks for everyone's patience. I'll leave this open if there are other questions that I haven't covered here.

Rob

Nice work, @robwormald

I'd like to add a few more observations:

Providers

Rob focused on _declarations_, _imports_, and _exports_. Module _Providers_ are different.

@Component.providers lives! Only @Component.directives and @Component.pipes are going away.

You can use both @NgModules.providers and @Component.providers. They have different purposes:

a) @NgModules.providers _extends_ the application by adding providers to the "main" injector (the app root injector for eagerly loaded modules). This is how the RouterModule adds a routing service to your app without you having to provide it.

b) @Component.providers _encapsulates_ service provision within the scope of the component instance (and its component sub-tree).

Both approaches meet different needs.

My general advice: _if you have a provider on @Component today, leave it there_

Feature Modules

They're a powerful way to organize your app. The Angular Module chapter has a short section on refactoring from a monolithic Angular Module to a feature module. It's pretty easy (at least it has been for me the several times I've done it.).

I look for natural seams in my app. I don't go nuts. I don't modularize every component.

The reason Material Design appears to do so is that they're trying to make it easier for us to use what we want in a fine grained way.

They'll have a kitchen-sink module that simply re-exports everything. You could just import that and be done. But if I'm tuning my own app, I create a similar _re-exporting module_ that exports just the MD parts that I want.

This is a pattern any library vendor could follow and might make sense in the enterprise where we put together our own company-approved "kits" of approved gadgetry.

Discovering declaration dependencies

The compiler does a much better job now of telling you when something is missing. If you follow the recommended practice and _always, always, always hyphenate_ your component selector names, the compiler will tell you when you've got an undeclared component. It also picks up undeclared/unrecognized data bindings and directives.

Migrating from pre-RC5 does promote a sense of loss. We have to do the tedious work of discovering dependencies in our previous directives and pipes lists. And we have to de-dupe. This is the essential unpleasantness of living on the edge.

An RC shouldn't change this much

Yup. You won't get an argument from the team on that. It wasn't intentional. Had we known we needed NgModule we'd have had it in beta.

But IMO, better to ship the right product than slavishly follow some notion of what an RC should be and deliver the wrong product. Also ... and it's not much comfort to say ... but if you've been watching some other major software companies recently you may have noticed an equivalently flexible notion of "release candidate". It's the way our industry goes for some reason.

I've converted a number of apps now and helped others do so. Once past the "_they moved my cheese (again)_" phase, it's a pretty mechanical migration and everyone seems to think they're in a better place. Maybe they're just being nice to me. OTOH, I don't hang out with people who are all that nice ;-)

(also, re: the name)
We bikeshedded names to death. They were initially called AppModules (since they describe something higher level than a component, an "App") but it wasn't a broad enough term. Packages, bundles, barrels, globs, etc.

Keep in mind that its not a replacement for ES modules - its an augmentation that will let tree shaking and ES modules work better for all angular developers, and semantically, they are similar to how ES modules work (imports, exports, declarations)

So, naming is hard. NgModule it is.

We're API complete for 2.0.0, and so we'll watch over the next few months after release to see what patterns develop, and where we can sugar and make better inferences for 2.1.

Thank you sincerely @robwormald @wardbell for the very insightful comments. I think for me the greatest relief is

"We're API complete for 2.0.0, and so we'll watch over the next few months after release to see what patterns develop, and where we can sugar and make better inferences for 2.1."

Its something we've already heard a few weeks ago but its now specifically in the context of NgModules along with all of this feedback. Its the confidence I needed to go to a Board and say. We're done here, time to finish building our application. I also want to extend my congratulations to the entire ng2 team and community on reaching this milestone! Exciting times ahead.

I honestly believe you can say that @SaltyDH. The Angular 2 churn is over.

That doesn't mean Angular 2 is done evolving. There will be future releases. But the churn leading to 2.0 ... is ... over!

Thanks a lot for the honesty and the explicit use-cases, guys. It really helps.

Just a quick thought on the naming and duplication in the API for NgModule, which I feel is a little cumbersome.

IMO this:

@NgModule({
  declarations: [ SharedComponentOne, SharedComponentTwo ],
  exports: [ SharedComponentOne ]
})
export class SharedModule {}

...could be clearer and more concise as:

@NgModule({
  private: [ SharedComponentTwo ],
  public: [ SharedComponentOne ]
})
export class SharedModule {}

1) WRT to naming (public and private vs declarations and exports), it is essentially how @robwormald above and I am sure many others are explaining it

2) (Ignoring naming) why would SharedComponentOne need to be repeated? Surely you could say if it is an "export" it must be a "declaration", so it could just be desugared that way?

Just my two cents on something highly subjective 😄 - thanks again for the detailed explanations!

@robwormald @wardbell Thanks for the detailed explanations.

And as I said earlier in this thread NgModules do help us organize better. A recent example is creating Validator Directives for template driven forms. Pre RC5 we had to import each directive on the component where we are creating a form. Now we just package it in VaildatorModule and import the Module wherever required. Any Validator that we add later on, is automagically available to the modules where I have imported ValidatorModule. I just need to update the template without worrying of dependencies.

@JamesHenry declarations, imports and exports are used to keep NgModules in line with ES6 modules as like we import, export and declare stuff in ES6 modules.
I agree with sugaring, things that are exported can be de-sugared into declarations auto magically.

@JamesHenry

1), we stuck with imports/exports because the mental model is closer to how es modules work (at least in terms of scoping) - you declare things in a module, import things from other modules, and export things to make them available to others.

2) if it is an "export" it must be a declaration, so it could just be desugared that way? -> this works until you use an ngModule to re-export, as in the MaterialModule example above - there's plenty of cases where you may use an ngModule to re-export something declared in another module, and then the inferences sort of fall apart.

@robwormald Great comments! definitely deserve a blog post.

I have a dialog library that uses @NgModule now and I can say that I see users struggling while trying to add custom components as they forget to register them in entryComponents, this is not clear enough apparently but understandable as it's an advanced feature...

I'm sure it will sink in with time

@shlomiassaf thanks!

Should mention as well, for cases like yours:

Notice that when you use angular's router, you add components to declarations and the route config, but _not_ to entryComponents, despite the fact that the router is the very definition of entryComponents. (remember - entryComponent === thing you want to refer to by Class, rather than selector)

There's a cool trick that any library can take advantage of - there's a magical token called ANALYZE_FOR_ENTRY_COMPONENTS - see https://github.com/angular/angular/blob/master/modules/%40angular/router/src/router_module.ts#L117 for how the router uses it.

So for libs that deal with dynamically inserted components, you could do something like:

@NgModule({
  providers: [ DialogService ]
})
export class DialogModule {
  static withComponents(componentList): NgModuleWithProviders {
    return {
      ngModule: DialogModule,
      providers : [
         { provide: ANALYZE_FOR_ENTRY_COMPONENTS, useValue: componentList, multi: true }
      ]
     }
  }
}

used like

@NgModule({
  declarations: [ MyConfirmDialog, MyQuestionDialog ],
  imports: [
    DialogModule.withComponents([ MyConfirmDialog, MyQuestionDialog ])
  ]
})
export class MyAppModule {}

Might work for your use case, might not.

@JamesHenry Very important that declarations is not confused with private. You're saying _This module declares this component_. You're not making any statement about public or private. You're making a claim of ownership.

It really is like what happens in ES6 modules. Anything you define inside the file "belongs" to the module defined by that file. Whether it is public or not depends upon your use of the keyword export.

And as with ES6 modules you _can re-export_ stuff that you imported.

I like to think of declarations as the "trick" that keeps me from having to physically place all of my components, directives and pipes in the same physical file (as you would have to do with ES6 modules if you intended all of those classes to belong to the same ES6 module).

So, for me, declarations is a substitute for pasting those files into one awful big file. That's _my_ mental model anyway.

One thing to note is the logical difference between providers and components in the context of a module.

Providers and Components are declared in the same place (NgModuleMetadataType) so a developer might intuitively feel that providers behave like components... which is thinking that a provider in a module will result in an instance for that module...

Since providers are managed by the DI this is of course not true, they are actually application level.
Components in a module are private if not exported, this might lead to the confusion.

I love the concept of modules, IMO the only issue in the API is having providers and components declared in the same place within a module... for new comers it's a hard thing to grasp.

@wardbell Thanks Ward that's really really great! Just to clarify as this just came up in discussions, when we say Angular 2 is API complete, which namespaces are we talking about here? @angular/???

@robwormald Thanks! A great tip!!!
A sugar flavoured one! will implement.

I'm not a Google employee and maybe a Google employee couldn't say. I can only report what I'm seeing with my own two eyes: a very serious API freeze across the @angular/ set of libraries.

There are good ideas that are sitting on the shelf because they weren't good enough or deep enough to justify holding the release. That is how it should be. Good ideas never cease. But the time has come to say _This is your Angular 2.0_.

We have the promised removal of deprecated API coming between now and "final". That has tweaks ... as anyone can see by looking at master. I feel like we're done.

@wardbell I echo what you say about exports.
The index.ts was once filled with

export * from 'a.component'
export * from 'b.component'
export * from 'c.component'
export * from 'p.directive'
export * from 'x.service'
export * from 'z.pipe'

is now reduced to just export * from my.module
rest all stuff clean sits in NgModule exports as
exports: [ AComponent, BComponent, CComponent, PDirective ] and so on

@wardbell I think @NgModules.providers should have been @NgModules.rootProviders or @NgModules.appProviders.

I feel it clearly describes the context of the providers.

@shlomiassaf That would be misleading. The effect of @NgModules.providers is different for eager and lazy modules. Lazy loaded modules get their own child injector which means that _their providers_ are added to the _child_ injector, not the _root_ injector. And so it goes if a lazy loaded a lazy loaded a lazy.

Maybe there could have been a different name. You know how _that_ goes. But rootProviders wouldn't have been an improvement for the reasons I just gave.

@wardbell agreed, didn't think about that scenario.

Just wanted to add to the sentiment that the thorough replies and communication are much appreciated. If this is indeed the last of the rc-to-final growing pains then we're off to the races. Thanks!

As others have remarked, the feedback from the team is appreciated. However, this kind of change during this phase of development is suggestive of fundamental design errors.
Let's be honest with ourselves, we have all made these kinds mistakes, so there is no value in being judgmental, but when I make a mistake it humbles me, and hopefully makes me subsequently more prudent. I do not get the impression that it is having the same effect on the Anagular team.

I like to think of declarations as the "trick" that keeps me from having to physically place all of my components, directives and pipes in the same physical file (as you would have to do with ES6 modules if you intended all of those classes to belong to the same ES6 module).

So, for me, declarations is a substitute for pasting those files into one awful big file. That's my mental model anyway.

@wardbell Maybe I am missing something fundamental, but I do not see how using NgModule.declarations is fundamentally different from importing all, but only re-exporting some, of your ESModules. Ironically, the primary limitation of ESModules is that they only provide for the concept of physical modules, not logical ones. I do not see how NgModule improves that. It is just a different aggregation syntax but does not raise the level of abstraction to a meaningful degree.

Also, NgModules completely fail to address a major problem:

The whole Function[] pattern is opaque in all the wrong ways. It drops discoverability to zero. One has to read the source to determine what is in an array of providers. The same is true of NgModule.exports.

But IMO, better to ship the right product than slavishly follow some notion of what an RC should be and deliver the wrong product. Also ... and it's not much comfort to say ... but if you've been watching some other major software companies recently you may have noticed an equivalently flexible notion of "release candidate". It's the way our industry goes for some reason.

@wardbell The biggest frustration about RC for me is the impression that a Beta just got pushed to RC because of ng-conf and google i/o. To make progress, the reason should be put clearly: its been _marketing_ and as technologists we should fight the trend of these flexible notions of maturity. You might know a popular car manufacturer in the valley where the discussion is about wether its been right to sell a beta for a product because it may took a life. I don't want to exaggerate, but as technologists we must speak up if that's the way our industry is going because then its going into a wrong direction.

I like the ngmodule approach but I have a question @robwormald:

I have a shared service which has no components, no directives and no pipes. It has only services (forRoot) and contains all business model classes. The app.module imports ServerApiModule.forRoot() therefore the services are available for the whole application. Should the feature modules, which uses the services and business model classes, import this shared module? Technically it is not necessary (no errors) but from the semantic view it would makes sense (also no errors). What should we do with that kind of modules? Import them or not? I personally like the second possibility because it says 'Hey I'm a feature module and I need this services and I need this business model classes'.

Thanks for an answer!

So I can feel the pain with libraries/modular application libraries and ngModule.

But as said many times don't mix two possible ways. That is and will be confusing.

So the correct approach would be to declare a ngModule for each component.
But as this makes it a lot more boiler coder for every component, that way doesn't work either on a large scale.

So way not add a new decorator called ComponentModule that is more or less sugar to avoid declaring component and module like material has to do.

Way this way? If I see a component. I know it MUST be part of a ngModule. If I have a case for many comonents to bundle them together I CAN use NgModule. If I just want a standalone component like the "old components" I use the ComponentModule and I KNOW all dependencies and that it can be a Module dependency for other modules, but not part of them

@nathraQ if the correct approach is to use NgModule for every Component, and it may well be, then NgModule should not have been introduced, and Component should have just been enhanced. Regarding boilerplate, Angular 2 is so heavy on it that it hardly matters at this point.

Many frameworks that don't have their own module system have very successfully used conventions to establish standard patterns for the layout and visibility of constructs. A wise man once said that you can't define conventions after the fact; that they need to be there from the start. I was skeptical of this claim, but this debacle proves he was right.

@aluanhaddad sorry I disagree. I had my use cases for only one module in angular 1.x with custom dependency management (like the starter of this thread), but I also had use cases for modules to bundle a set of controllers, directives and services.

Both is useful. Now we have to figure out how to integrate it understandable for everyone

Wow, that was a long read :smile:

I can see the pros and cons of the new change, but it did make me think about one scenario I had trouble with in Angular 1.

@wardbell @robwormald -

If I use the SharedModule pattern, and I import two 3rd party libraries (ui-bootsrap vs angular-strap anyone?) which both use the same selector for a component , let say my-selectbox

I'll get an error if I'll try to import them both? right?

One solution I read here is to re-export one of the libraries in a custom module
But that means I need to keep updating the wrapper module every time the library upgrades no?

Also, It can happen I have my own component selector with the same name ( and in big projects that might happen more than once)

Is there a recommended solution to this problem? or is it the "wrapper module" ?
(I hope we won't come back to using a prefix for selectors, that was no fun)

Thanks in advance!

Namespacing, or prefixing as some call it, is generally a good idea, no matter how large your project is. Yes, it may be more trivial in smaller projects, but as soon as thrid-party modules are involved I would say it's almost a requirement—at a minimum it provides peace of mind knowing that collisions won't happen even as other modules get updated, but also developers can easily identify what belongs to each module without necessarily having to follow the dependency tree.

@nathraQ All of these patterns can be achieved with ECMAScript modules. The introduction of NgModule gets us nowhere and the fact that libraries like AngularMaterial are turning Components into NgModules just to preserve the previously provided encapsulation just proves the point.

@aluanhaddad NgModules is trying to do something very different than ES modules, and are not replacing the Component encapsulation either, i.e., both ES modules and Components have not changed their core responsibilities. NgModules is the medium through which you describe architecture throughout your application, and this helps Angular better understand your intent and optimize accordingly, allowing for things like compiling certain portions of your application beforehand, serving dynamically resolved modules from a server, and so on, all of which cannot be achieved with ES modules. For the same reasons one NgModule per Component isn't the rule of thumb to follow.

It is true that simpler applications may not benefit as much from those benefits, but in those cases NgModules should be less of an "annoyance" anyway.

@emilio-martinez , as @aluanhaddad wrote, ES6 and the "old angular 2 way" gave us the namespacing we needed.

I've worked on very large projects with Angular 1, having a selector named: mb-product-list can collide very quickly with others if it's a large project (Even in team of 5+ developers).

Trying to solve it with more namespacing you end up with: mb-somefeature-product-list which makes templates look dirty.

I was very happy to see it was solved in ng2 because of the directives metadata.
You could npm install any package you like, and import only what you need from it per component.

ngModules has it's benefits that's for sure, it helps with loading chunks async and also help us write less imports and improves productivity.

But using the SharedModule pattern introduces a global namespace, much like we had in ng1.

At least with providers, it is namespaced by the ES6 token, the file location.

With components, because of the selectors, it will scream that there are two components with the same selector.

I wish we had some easy way to configure a special namespace for use cases of collisions with 3rd party or local shared components.

Something like "selector override".

That's what I was asking about

Thanks @robwormald for your detailed insight, but one thing from newbies like me(Mostly, I am a backend developer). I am learning Angular 2 and with typescript. So ES6 module concept is kind of blurry to me
We have a complex application structure. Its an analytics app, where my component count is almost 60.
And I think it is well strucutred under RC4. Apart from Login we didnt use any kind of routing, because routernrecreates the whole component. So We are planning as Tab based app.Two main tab is there (1.Analysis 2.Dashboard).
Analytics Tab will have multiple tabs each having single analysis under it. Dashboard will also have
multiple tabs but each tab will consists of multiple analysis which were save under
the analytics section. So going back and forth from multiple tabs( under both dashboard and analysis)
and also switching between Dashboard Tab and Analytics Tab, we feel routing wont serve
our purpose(correct me if I am saying something dumb).
Now the RC5 NgModule kind of breaking our application. We really dont know how to
redesign our application. Can we really use AoT compilation in our application? Isn't
the whole AoT thing is based on routing?

@shairez NgModules provide exactly this kind of namespacing. Let me try to clarify this a bit further...

The key thing to realize here is that a component is compiled, more or less, "inside" the module it's declared in - see https://plnkr.co/edit/9w10b1Y8Bjr5DDIxOwnC?p=preview for an example.

Notice that there are two conflicting selectors (my-generic-selector) - each of the feature modules imports one of them, is able to use it internal to that module, without polluting any "global" namespaces.

So used like

<my-app>
  <!-- belongs to FeatureModuleOne -->
  <feature-one></feature-one>
  <!-- belongs to FeatureModuleTwo -->
  <feature-two></feature-two>
</my-app>

expands to

<my-app>
  <!-- belongs to FeatureModuleOne -->
  <feature-one>
    <!-- the generic imported in FeatureModuleOne -->
     <my-generic-selector></my-generic-selector>
  </feature-one>
  <!-- belongs to FeatureModuleTwo -->
  <feature-two>
    <!-- the generic imported in FeatureModuleTwo -->
    <my-generic-selector></my-generic-selector>
  </feature-two>
</my-app>

without any conflicts, because when the compiler compiles feature-one and feature-two, it does so _in the context of the module they belong to_, which avoids polluting a global scope.

@robwormald Sure, that is awesome and is better than Angular 1 in that sense.

The use case I wrote about was referring to the pattern of using a global SharedModule as suggested in the docs.

If I'll try to declare both GenericSelectorFeatureOne and GenericSelectorFeatureTwo insided of the SharedModule, I'll get an error right?

Or if SharedModule has a whole bunch of useful common components and I want to import it to every feature module.

If my feature module has a colliding selector, or some 3rd party has a colliding selector with one of the existing libraries exported in the SharedModule, it'll issue an error right?

@shairez I think most applications will end up with a number of "SharedModules", and the likelihood of collisions is low - modules are cheap, so there isn't a big cost for having lots.

If you have ideas on how to improve the docs then a PR would be gladly welcomed.

Thanks that is the answer I was looking for, I'll test it out.

I don't have yet better ideas on how to solve this with the new way of writing modules, that's why I asked it here, but once I'll test the multiple shared modules solution I'll have more material to discuss it with @wardbell and submit a PR.

Thanks for the help @robwormald !

From the latest docs available on https://angular.io, there are _many_ warnings about anti-patterns and bugs that can easily emerge if NgModules are composed incorrectly. For example

Do not specify app-wide singleton providers in a shared module. A lazy loaded module that imports that shared module will make its own copy of the service.

That is rather troubling. If you follow the tried and true approach of _first make it work, then make it fast_, you will likely need to adjust which modules are eagerly or lazily loaded based on empirical metrics and the emergent structure of these modules as your application or library expands. Basically, why should a module care if it is loaded lazily or eagerly?

Another, seemingly more serious issue arises from juxtaposing the following sections of the docs

Suppose a module requires a customized HttpBackend that adds a special header for all Http requests. If another module elsewhere in the application also customizes HttpBackend or merely imports the HttpModule, it could override this module's HttpBackend provider, losing the special header. The server will reject http requests from this module.
Avoid this problem by importing the HttpModule only in the AppModule, the application root module.

Can I re-export classes and modules?
Absolutely!
Modules are a great way to selectively aggregate classes from other modules and re-export them in a consolidated, convenience module.
A module can re-export entire modules which effectively re-exports all of their exported classes. Angular's own BrowserModule exports a couple of modules like this:
exports: [CommonModule, ApplicationModule]
A module can export a combination of its own declarations, selected imported classes, and imported modules.

So on the one hand, the docs discourage users from re-exporting certain modules, while at the same time stating that doing so provides a great convenience.

The notion of NgModule re-exports is analogous to that of ES Module re-exports except that you have less control. JavaScript is statically scoped (yes I know this is not) and that is one of its greatest strengths, allowing for extremely flexible composition, and information hiding. JavaScript also supports shadowing so you always have a way to override a scope.

The biggest problem though, is that the examples in the guidelines all revolve around how to import the @angular/* framework modules. These are naturally dividable up in ways that allow conflicts to be intuitively prevented. This is because they exist to provide infrastructure level services that do not generally overlap. User defined modules can cut across many layers and may want to enhance or change a variety of behavior. For example, a logging module might want access to the Router and Http, services while at the same time providing several components for displaying info to administrative users and use one of the forms modules to define them.

The docs saying nothing about if re-exports are transitive... Module A re-exports CommonModule. Module B re-exportsModule A. Module C imports Module B. Does that mean, that Module C can now use directives from CommonModule?

@Martin-Wegner They are transitive as described here https://angular.io/docs/ts/latest/cookbook/ngmodule-faq.html#!#q-re-export

@aluanhaddad where? I can't find any word about transitive re-exports with more than one hop...

Reading what @aluanhaddad grabbed from the docs of angular I feel like I have to reconsider everything I've learned about Angular 2 and maybe even if there isn't a framework that handles dependencies in a better way.

As I understand it. If I have an App Module importing HttpModule and I have a Feature Module with a service, that uses Http Service, I shouldn't import HttpModule on Feature Module level but on App Module level. So what if I create a Feature Module that is shared among many App Modules? I have to really on HttpModule beeing imported at the App Module. I can't tell, that my Feature Module depends on the HttpModule. That's really ugly and means, that NgModule definition lacks a lot of features like for example PeerDependecies.
Oh boy. It feels like angular 2 is breaking apart. I fear it's time to start over, abandon angular 2 and start with angular 3.

There's actually so much sense in what you just said. I started this
angular2 journey since last year and haven't had any reason to feel beating
despite all the breaking changes we've seen so far (from alpha, to beta,
and RC). It understandable at those phases and since it religiously stuck
to the philosophy it used in converting me out of my minimalist Backbonejs.
The whole idea of this ngModules to me has proved somewhat counter
productive. And its sad to watch the whole time I put into evangelising the
rich goodness of the minimalist and bloat-free "Components" go to
absolutely waste.
On Aug 29, 2016 9:44 AM, "Daniel Schuba" [email protected] wrote:

Reading what @aluanhaddad https://github.com/aluanhaddad grabbed from
the docs of angular I feel like I have to reconsider everything I've
learned about Angular 2 and maybe even if there isn't a framework that
handles dependencies in a better way.

As I understand it. If I have an App Module importing HttpModule and I
have a Feature Module with a service, that uses Http Service, I shouldn't
import HttpModule on Feature Module level but on App Module level. So what
if I create a Feature Module that is shared among many App Modules? I have
to really on HttpModule beeing imported at the App Module. I can't tell,
that my Feature Module depends on the HttpModule. That's really ugly and
means, that NgModule definition lacks a lot of features like for example
PeerDependecies.
Oh boy. It feels like angular 2 is breaking apart. I fear it's time to
start over, abandon angular 2 and start with angular 3.


You are receiving this because you are subscribed to this thread.
Reply to this email directly, view it on GitHub
https://github.com/angular/angular/issues/10552#issuecomment-243066961,
or mute the thread
https://github.com/notifications/unsubscribe-auth/AF675h8_np9i5cHgL8mMOOu8vMMQmWKkks5qkpv8gaJpZM4Jee-o
.

Could somebody correct me if i'm wrong.

I'm thinking on organizing an app as follows.

app
|--Law/
|----ManyComponents.
|----LawNGModule.
|--User/
|----ManyComponents
|----UserNGModule
|--AppNGModule
|--SomeFirstLeveComponents

Let's say I want to work with Angular Material. Before I kinda hated that I had too much boiler plate importing each element in every component. Now I would add it to the NG module. But since I decided to use one ng module per feature, I have to do the imports for each ng module I consider. Not just the root one. Is that right?

Or maybe this is a case where you create a module x to reexport y(Material) another module, and you import it in the modules you need?

@ReneVallecillo You are right. You have to Import it in each feature module. Or you add it together with other modules into a shared module (hiding the dependency), that reexports it and than use this shared module. And if you reexport it an another module you might even have duplicate imports without knowing and seeing it immediately.

@robwormald In response to your comment above:

In AoT mode however, this works a bit differently - at build time, we statically (that is, without executing your code) extract the same metadata from the source code by scanning for decorators.

Does this mean that you are statically extracting modules from the original source code, and as such, we effectively cannot create modules dynamically?

In order to make the migration easier, I was thinking about creating a function that would create modules dynamically; something like this:

function createModule (entryComponent: Type, dependencies: Type[]) {
    @NgModule({
        imports: [CommonModule, FormsModule],
        declarations: [entryComponent, ...dependencies],
        exports: [entyComponent]
    })
    class FeatureComponent {}
    return FeatureComponent;
}

So while this (likely?) would work with JIT compilation, it would not work with AoT because AoT statically parses the source code, looking for decorators?

This needless complexity in an era of ES@next.... @aluanhaddad makes some very good points.

As things stand, I can't possibly see my self/team going forward with Angular 2 if this is the intended direction. And it seems it is, as this has been "closed".

I may have to do like @DaSchTour and research other front-end frameworks. Too bad, because a few months ago, NG2 was quite the joy to work with and my obvious pick from the litter.

@iyobo other than the initial pain of changing over, the _vast_ majority (and I talk to a _lot_ of developers) of feedback on NgModules has been positive.

Looked at in isolation in a hello world app, you could argue its "complex" ("needless" is factually incorrect, per the above explanation), but they really come into their own in a real application - organizing features, lazy routing, and AoT are all made so much simpler with NgModules. Given the choice today, I'd still opt for adding NgModules into the framework.

I'm with @robwormald here, the initial pain of migrating from RC4/5 to NgModules was manageable after seeing the advantages of not having to import every single component/pipe into a newly created page.
NgModules start to shine after getting a little more complex and having lots of shared components.

So much easier to just import SharedModule and be done with it.

Hey @robwormald, The notion that one disagrees with the current direction of Ng2 is in no way a factual assertion that they only work on "hello world" level apps.

This "compartmentalization of features" you speak of is nothing new. It is something that has always been designed around as needed to suit each individual product/solution using components.
That said, there is still really no greater compartmentalization than a component declaring what it needs by itself.

Now as for lazy loading, pulling back and looking at things from a bird's eye perspective, the major boon of lazy loading is to improve loading speed of the app. I think the majority of us have pipelines that make this work. Enter minification, compression and multiple app entry points ala webpack.

It just seems to me that NgModules is a solution in search of a problem, than a solution to an actual problem. But then again, I can't claim to know everything...

I think that time will show if introducing NgModules was a good idea. I have the impression that Angular 2 is currently just made to reach certain design goals. Nobody has thought about things like teams with different skill levels, code that is getting older and that is passed through generations of developers. In an ideal world it may look like a great idea. But in reality NgModules introduces a lot of traps and obfuscation that will cause a lot of trouble in the phase of training and introduction into a new project. It's simply not that simple and intuitive as it was with declaration on component level. Dependencies are hidden away in modules and it's just a matter of time you have to start researching for the module you component is in.

Having made the transition of a larger application to NgModules recently, I think that they are mostly a good thing (now that I’m done with it, it makes certain sense). I actually think that the major problem is that they require a different component structure, something that likely didn’t fit well with how you structured your application before (at least that was the case for me).

However, I feel like they shouldn’t exist as the sole solution. If you look at how components are very commonly built, then you realize that very often, they are composes of smaller highly specialized subcomponents. For these subcomponents, which have no purpose outside of that container component, having to maintain them in the higher scoped module is really tedious and can quickly yield to scoping problems.

I would really appreciate if there was a mechanism _in addition_ to NgModules that allowed components to define other components or directives as locally scoped dependencies which only apply within that exact component (just like how the directives listing worked before modules).

I like @poke 's suggestion of having an alternative for those who would rather not go Full NgModules.

NgModules were added in RC5 to solve problems with getting compilation and lazy loading to work - so hardly part of the original master-plan which is why they are not a great fit for some use-cases (especially if you were building an app based on the original "components are king" design).

Of course they fix or enable some things but bring their own complications along as well - more things for people to learn and design around which is especially challenging when all the best-practices and approaches haven't been worked out yet. Early adoption can bring lots of pain, I learnt that from the ride to RC4.

I liked the original component-centric plan more which is probably why I'm finding Polymer / Web Components a better fit now. Modules just feels like a half-step back to Angular 1.

At first I was resistant to this change of removing pipes and directives but getting used to it now and it does not seem as bad as I first thought.

Can anyone show me how NgModules works in a really complex app where multiple component requires multiple others at multiple levels? All tutorials, examples and repos I saw only shows the easy ways where a module encapsulates it's own stuffs and publish (exports) some of them to others.

In real projects there are many cross-required dependencies usually with a hierarchical chain. Its somehow false to prove a concept of something by an example designated exactly for that.

@for all author of Angular2: can you please tell us honestly what are the negative parts of NgModules? I know you can write a book about all good things. Its fine. But I always need to deal with the hidden bad things which are wouldn't clear if you won't talk about them.

Can anyone show me how NgModules works in a really complex app where multiple component requires multiple others at multiple levels?

One way to do this would be to create a dependency module just for those components: Let’s assume you have three sets of components A, B, and C which contain multiple components each and each set has those which are somewhat related. So these three sets would work well for three separate modules.

Now, components in each of those sets require multiple components from a set D. Those components in D are only used for the components in those three sets. Since they are used in all of them, you cannot just add the components to those modules (since components may only be part of a single module). At this point, you could merge A, B, C, and D into a gigantic module, so all the dependencies are there. But that’s of course very messy. Instead, you just create a new module for D that just contains those dependencies. This module does not do anything other than providing access to those modulew. Now you can import that module in each of those three other modules, and are able to use the components. But because the components are still “private”, you do not reexport the module or import the module D in some other module.

Since module imports only affect the module itself, this allows for some sort of scoping without polluting other modules. Of course, it requires you to create more modules but that’s just how it works.

I can share my current setup. I have used 3 modules in app: CommonModule, AppModule and TestModule.

  • CommonModule imports & exports most common stuff like HttpModule, FormsModule, MdInputModule etc
  • AppModule imports BrowserModule, CommonModule, app.routing and individual components
  • TestModule imports & exports BaseModule, but overwrites some providers, like XHRBackend with MockBackend

I have introduced this setup to simplify TestBed.configureTestingModule,
so that I have to import TestModule and then just single component like:

TestBed.configureTestingModule({
  imports: [ TestModule ],
  declarations: [ MyFormComponent ]
});

One additional downside that became glaringly clear as I migrated from RC-4 to release was that NgModules impose a heavy penalty for simple refactorings where a component simply needs to be split up. With NgModules, you have to change the containing module, which might be several levels up the conceptual component tree, or promote the component by wrapping it in an NgModule and then removing it from its parent NgModule. Refactoring components is essential.

This is directly related to @poke's point

I would really appreciate if there was a mechanism in addition to NgModules that allowed components to define other components or directives as locally scoped dependencies which only apply within that exact component (just like how the directives listing worked before modules).

I strongly disagree. A modular architecture as it is proposed by Angular 2 is easier to scale, adjust, and refactor where necessary. Sure, from RC4 to RC5 there was a bit of adjusting, but if anything, to me NgModules have proven to allow for a much more flexible application.

Angular is opinionated and it certainly may not be one size fits all, but NgModules is just as certainly not the point of failure to architect a smart, modern and highly-performant application.

@emilio-martinez: In my opinion, NgModule would never be introduced, if Angular 2 weren't so slow at bootstrapping, when involving JiT. All other 'improvements' like 'scaling, adjusting, and refactoring' are arguable, as this discussion shows.

It's been quite a while now and many of us have had time to fully absorb NgModule into our work. I think it is now clear that as quite a few people have described, once you are past the speedbump of moving up to the new module system, it enables some pretty great things. For anyone who rides this thread having trouble absorbing NgModule, I suggest scrolling all the way through and reading everything from @robwormald and @wardbell especially.

I believe we will all find a year from now that many Angular 2+ applications make pervasive, seamless use of modules, lazy loading, and AOT. I believe it will be completely routine for most or nearly all applications to use these things to implement the "progressive application" vision where even large complex applications have a near instant initial load time and then lazily load (or optimistically lazily preload) the functionality they need. The result is actually quite amazingly slick, and achieved at remarkably low cost for the individual application developer: NgModule basically is that cost, and it is an irritating transition but then only a very modest amount of ongoing work to use.

@kylecordes I hope you're right and I think that's the correct attitude to have.

@iurii-kyrylenko that is very true.

I do have a problem with angular compiling my JavaScript as it's supposed to TypeScript compiling my JavaScript, but that's a separate issue.

@kylecordes I think there is a lot more to consider than just the transition. Modules introduce a lot of complexity and a lot of additional possibilities to add bugs to the own application. The biggest problem is obfuscation of dependencies. Which will cause a lot of trouble in the next years of developing with angular 2.

@aluanhaddad I believe Angular uses a tsc wrapper to compile. It's nice because you can even implement it into a task runner workflow, for example.

@iurii-kyrylenko bootstrapping speed is also hard to determine based on RC4. A lot of the work that was done from then to final release has been cleanup and optimizations. In my experience Angular compiled JIT runs quicker than RC4 did anyway.

@DaSchTour can you elaborate on the bugs you've found while working with NgModule?

@emilio-martinez it's not bugs in NgModule but bugs that will arise because of missing imports or duplicate service instances that would have been omitted or found in an earlier stage of development. It's about importing things in the place I use them and not somewhere I don't see if it's needed or used and in what place it is needed and used.

Just think about TypeScript working this way. I have a base file for my module, let's call it _index.ts_ it looks like this.

import {foo} from bar;
import {StartComp} from start;

StartComp.boot();

Than we have a file called start.ts that looks like this.

export class StartComp {
   public static boot() {
      foo()
   }
}

That is what Angular is doing with NgModules. With some magic I have imported something into a module and it appears on the other end of my application. You have to know that foo is imported in index.ts and by running StartComp from index the imports there can be used in the component.

The dependencies are hidden and need additional investigation to find them.

@emilio-martinez

In my experience Angular compiled JIT runs quicker than RC4 did anyway.

I have a medium complexity project, based on MEAN stack and Angular 2 final. It takes about 10 second to complete bootstrap in JIT mode on my Android device. I consider this as a significant delay. Currently I can't use AOT compilation without modifying my source code (issues with private members, elvis operator ...).

Does anyone have any info on performance AOT+lazy load for a real life projects?

I believe Angular uses a tsc wrapper to compile. It's nice because you can even implement it into a task runner workflow, for example.

@emilio-martinez this is exactly what I do not want. I want to compile my code using whatever version of TypeScript I please, for example 2.1.0-dev which has down level emit for async/await. I don't want Angular to be in charge of compiling my TypeScript files, this should not be delegated to a framework, it is the role of a language. Without getting further off topic I would not have touched @Script with a ten-foot pole, thank goodness it's dead.
Workflow wise, I use JSPM and at times Webpack, not a traditional task runner, and I let my IDE handle my linters.

Another issue is that I have written decorators that abstract over angular decorators and I now understand that aot will ignore them. Considering how heavy angular's of decorators is, I was very disappointed to learn that the framework would not fully support decorators, treating them, during AOT, as static annotations when they are in fact a runtime construct in the underlying language.

@aluanhaddad its important to understand that there are two distinct steps occurring during AoT compilation - the first is generating _new_ typescript code, the second is transpiling that code to ES5/6. ngc does both as a convenience, but there's nothing inherent in AoT that requires that. Inside of google, we do both, and so that case is the priority. Anyone could implement a compiler host to support code generation in any environment they wanted.

We do not (yet) support abstracting on top of angular's built in decorators, but if you wanted to use something like https://www.npmjs.com/package/core-decorators, that would be fine.

@iurii-kyrylenko suggest you watch the day one keynote of AngularConnect, where LucidCharts talked about their experience with AoT. See https://youtu.be/xQdV7q3e_2w?t=1411

IMHO - everybody's #1 goal should be getting on AoT compilation as soon as possible. The performance is simply unbeatable.

@robwormald I haven't looked at what options are available when calling ngc - so this might already be covered. I think that those options could ease the concerns like the ones voiced here, if they make it obvious that NGC is fulfilling the first purpose as its main reason to exist in the second purpose as a convenience/optimization. If the documentation or help shows how to separately run off-the-shelf tsc for those who prefer to do so, that could further ease the concerns?

@kylecordes i don't think the documentation is going to cover how to implement your own compiler host any time soon. Its an advanced use case, and thus would require some self-directed learning to implement. We implemented a similar thing for the CLI here https://github.com/angular/angular-cli/tree/master/packages/webpack

@robwormald Ah, I don't mean implementing your own compiler host. I just meant a two-line "build process" - first calling ngc to emit some generated typescript, then calling tsc yourself to compile all of the typescript (your source plus the generated source) to JavaScript. This provides a quick reassurance that the typescript code is merely being compiled to JS by the off-the-shelf typescript compiler.

@robwormald Thank you for your reply.
Regarding NGC, what I want to know is whether I can control the TypeScript compiler version and settings in the TS -> TS pass. Can I pass TypeScript to NGC or do I have to use a specific version which wraps a specific TypeScript version? How coupled are they?

Regarding decorators, is there an issue tracking support for user defined decorators that abstract over Angular decorators? The https://www.npmjs.com/package/core-decorators is an orthogonal set of decorators, but I have decorators which enforce patterns and conventions in my Angular apps by wrapping Angular decorators. An obvious use case for this is to automatically create and enforce package wide component name prefixes, but there are others as well.

Since NGC does not support this, how does it know which decorators are Angular specific?
Does it match angular decorators by name?
I hope not because that would violate JavaScript lexical scoping.
A simple scenario
_awesome-component-decorators.ts_

import { Component } from '@angular/core';
import template from './awesome-component.html';
import style from './awesome-component.less';

export const awesomeComponet = <T extends new (...args) => any>(target: T) =>
  Component({template, styles: [style], selector: snakeCase(target.name) })(target);

_consumer.ts_

import { awesomeComponet } 'app/shared/awesome-component-decorators';

@awesomeComponent 
export class AnAwesomeComponent { }

@awesomeComponent 
export class AnotherAwesomeComponent { }

@jpsfs Have you found any solution to load Components dynamically with out adding component declarations to root application module ? .

I am also part of a angular 1.X to Angular 4 migration project . This project has large number of components that are reused in different application that are lazy loaded on based on application context.

As per my understanding in Angular 4

We have to add component dependencies into root @NgModule declarations as below.

import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {Component, NgModule} from "@angular/core";
...
...
@NgModule({
imports: [BrowserModule ], // import Angular's BrowserModule
bootstrap: [BootStrapComp], // indicate the bootstrap component
declarations: [com1, comp2 , comp5 ...... Comp n ] // register our component with the module
})
export class AppModule {}

platformBrowserDynamic().bootstrapModule(AppModule);

But in our case we don't wanted to root NgModule to know about component dependencies in compile time . Rather we wanted components to be loaded dynamically in Run time .
Did you find any good solutions that can bootstrap application without adding all components into root NgModule declarations ( And we don't wanted to have one NgModule for each component too:) )

@DaSchTour

Remeber the times your app entrypoint looked like this?

Well for that some of us used a build scripts to automatically require these modules and add them to the app module.

I'm looking for something similar and easy solution in angular2.

@samudrak you cannot lazy load just the component if u want to use Angular aot support. You will need to setup lazy module for each component and lazy load the module. We use similar approach in our application...

With dynamic imports support in ECMAScript and now in TypeScript (with full type checking orthogonal to loading), the lazy loading use case is fairly arbitrary. Of course hindsight is 20/20 and there was no way to know that would happen.

This issue has been automatically locked due to inactivity.
Please file a new issue if you are encountering a similar or related problem.

Read more about our automatic conversation locking policy.

_This action has been performed automatically by a bot._

Was this page helpful?
0 / 5 - 0 ratings