Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why subcomponents in Angular should be registered via NgModule? What about encapsulation?

Tags:

angular

I have some experience with other frameworks and architectural principles, and I absolutely don't understand the decision of Angular Team to deprecate the directivies property of a Component in RC6 in favor of declarations in NgModule.

Usually the architecture is about encapsulation, but suddenly if some functionallity of a component should be encapsulated by a child component, the subcomponent "leaks" up to the module, where it now should be registered.

  1. So why by having a logical tree of components in a module, all of them should be registered "plain" inside NgModule?

  2. Does this approach don't blow the ngmodule with a lot of imports and delcarations?

I understand that we can split everything into multiple modules, but inside a single module such global "load and register" remembers me to bunch of global script [src] tags inside html. I thought we have moved away from this pattern, but it looks like Angular returns to it.

It seems, I miss something here, could somebody please explain me?


[EDIT 1] (breaks AOT compilation, see edit-2) Right now, we mimic nested components declaration, so that each component exports list of used components, and then in NgModule we go though all root components, collect their dependencies and prepair full declarations list.

It looks like this:

src/app    
    - /components/home-view/
        - /toolbar
            /menu-button
                - menu-button.component.ts

            - toolbar.component.ts

        - home-view.component.ts

    - app.module.ts

app.module.ts

import { HomeViewComponent } from './components/home-view/home-view.component';

namespace Utils {
    export function flatternDirectives (arr: any[] = []): any[] {
        const declarations = arr.reduce((compos, compo) => {
            compos.push(...flatternDirectives(compo.directives), compo);
            return compos;
        }, []);
        return Array.from(new Set(declarations));
    }
}
@NgModule({
    declarations: Utils.flatternDirectives([
        HomeViewComponent,    
    ]),
    bootstrap: [AppComponent]
})
export class AppModule { }

./components/home-view/home-view.component.ts

import { ToolbarComponent } from './toolbar/toolbar.component';

@Component({
    selector: 'app-home-view',
    template: `<app-toolbar></app-toolbar>`,    
})
export class HomeViewComponent {
    static directives = [ ToolbarComponent ]
}

./components/home-view/toolbar/toolbar.component.ts

import { MenuButtonComponent } from './menu-button/menu-button.component';

@Component({
    selector: 'app-toolbar',
    template: `<app-menu-button></app-menu-button>`,    
})
export class ToolbarComponent {
    static directives = [ MenuButtonComponent ]
}

./components/home-view/toolbar/menu-button/menu-button.component.ts

@Component({
    selector: 'app-menu-button',
    template: `<button></button>`,    
})
export class MenuButtonComponent {}

Are there any caveats in such approach?


[EDIT 2] In DEV the approach above worked fine, but AOT compilation breaks with the error.

ERROR in : Cannot determine the module for class 'name' in 'path'! Add 'name' to the NgModule to fix it.

So we must move back to plain declarations. Any solutions how tofix the aot compilation?

Thank you.

like image 857
tenbits Avatar asked Jun 20 '18 14:06

tenbits


1 Answers

  1. NgModule concept came late right before release, it provides standard way to create libraries/modules, adds effective import system and lazy loading feature. In many ways it similar to any other module/package system (i.e. declare module dependencies, module public exports etc.) with some limitations due to JS nature.

  2. You can choose the granularity level when designing modules to avoid verbose imports/declarations. Libraries tend to use module per component strategy to minimize size impact (you import exactly what you need, no more), but sometimes you have to import many modules in this case.

  3. The problem is that in AOT all component references must be resolved statically during compilation, you can not have dynamic code such as your flatternDirectives function since it is not statically analyzable. See https://angular.io/guide/aot-compiler#metadata-restrictions, I doubt that it is possible to write function that can remove duplicate entries, but probably you could use even duplicated entries i.e. declarations: [HomeViewComponent.directives] (need to check generated code).

like image 141
kemsky Avatar answered Oct 22 '22 04:10

kemsky