isaacplmann / ngx-contextmenu

An Angular component to show a context menu on an arbitrary component
MIT License
248 stars 91 forks source link

Extending ContextMenuService and for another module to use #94

Closed Jamlearner closed 6 years ago

Jamlearner commented 6 years ago

Hi @isaacplmann ,

If I want to extend ContextMenuService,

@Injectable()
export class CustomContextmenuService extends ContextMenuService {
.....
.....
}
@NgModule({
    exports: [
        CustomContextmenuService 
    ],
    declarations: [
        CustomContextmenuService 
    ]
})
export class MyModule {
}

In AnotherModule, I imported MyModule so I can use CustomContextmenuService in AnotherModule? But I met error of Unexpected value 'ɵa in MyModule ..Please add a @Pipe/@Directive/@Component annotation.... when doing npm start Did I missed out something?

isaacplmann commented 6 years ago

If you want to wrap the service in a module, put it in the providers array.

@NgModule({
    providers: [
        CustomContextmenuService 
    ],
})
export class MyModule {
}

Or you could use it directly like this in your AppModule:

@NgModule({
  imports: [
    ContextMenuModule.forRoot(),
  ],
  providers: [
    // This replaces all instances of ContextMenuService with your CustomContextmenuService
    { provides: ContextMenuService, useClass: CustomContextmenuService },
  ],
})
export class AppModule {}
Jamlearner commented 6 years ago

Thanks for your comment!

1) REPLY to: If you want to wrap the service in a module, put it in the providers array.

Relates to a recent closed issue on CustomMenuComponent, I tried to add CustomContextmenuService to the component but it seems visibleMenuItems:Array is empty when doing right-clicking.

@Directive({
  selector: 'customcontextmenu',
  // normal stuff
})
export class CustomMenuComponent {
   constructor(
   @Host() private contextMenu: ExtendableContextMenuComponent,
   private contextMenuService: CustomContextmenuService , <----- i added the custom service 
   /* whatever other things you want in here */) {
  }

  // normal life cycle stuff works, reference this.contextMenu to modify the context menu
  // i also added contextmenu event to listen, and to call onContextMenu()
    onContextMenu($event: MouseEvent, item: any): void {
        this.contextMenuService.show.next({
            contextMenu: this.contextMenu,  <------- <<EMPTY>> visibleMenuItems:Array(0)
            event: $event,
            item: item
        });
        $event.preventDefault();
        $event.stopPropagation();
    }
}

How can I make this.contextMenu to use CustomContextmenuService instead of ContextmenuService? I am guessing this the problem occuring here? I move on to test using the original ContextmenuService instead of CustomContextmenuService and it will work. Is it possible to use CustomContextmenuService in CustomMenuComponent ?

2) REPLY to: Or you could use it directly like this in your AppModule:

I also tried with provides: ContextMenuService, useClass: CustomContextmenuService but compile error of No provider for CustomContextmenuService!

providers: [
        { provide: ContextMenuService, useClass: CustomContextMenuService }
    ]
Jamlearner commented 6 years ago

Hi @isaacplmann, I have used useExisting instead of useClass to resolve the above issue. Thanks for the tips!

    providers: [
        CustomContextMenuService,
        { provide: ContextMenuService, useExisting: CustomContextMenuService }
    ]

It also appears that I am unable to do a proper manual contextmenu listen in AppComponent with the extended wrapper service contextMenuService: CustomContextMenuService. The item data went missing...

    ngAfterContentInit() {
        this.renderer.listen(this.target, 'contextmenu', (event) => this.onContextMenu(event, this.items));
    }

    onContextMenu($event: MouseEvent, items: any): void {
        console.log('items', items); <---- this shows the items correctly
        this.contextMenuService.show.next({
            contextMenu: this.basicMenu,
            event: $event,
            item: items <----
        });
        $event.preventDefault();
        $event.stopPropagation();
    }
                    <customcontextmenu  #basicMenu>
                        <ng-template contextMenuItem let-item>
                            I am {{ item?.name }}
                        </ng-template>
                    </customcontextmenu>

When I right click on the target, menu item shows "I am " instead of "I am Jam". It seems like my item get lost somewhere else.

isaacplmann commented 6 years ago

It could be that your CustomContextMenuService is overriding a method that is supposed to pass on that item data. Since you're changing internals now, it's hard for me to debug without looking at your code.

I'm curious why do you need to extend the service and component? If there's some functionality that you're adding which other people could benefit from, we could talk about submitting a PR.

Jamlearner commented 6 years ago

As a new Angular self-learner, I came across an topic that interests me into doing some dependency injection, directives/components/services wrappers on third-party angular libraries. Looking at your past issues here, I picked up some ideas on extending your directives/components/services as a practical training. Example, allowing users to do a contextmenu event binding via an API, keyboard bindings etc in your library instead of writing the same chunks of codes in their application. I do not know if this is a good idea but it's a basically a fun topic to play with.

Though my CustomContextMenuService is still empty for now. I am making sure that the original features are working with CustomContextMenuService before I start writing and testing my custom services/controls.

And so I have an empty CustomContextMenuService

export class CustomContextMenuService extends ContextMenuService {
//empty
}

In AppComponent I am just attempting to attach the event binding

    onContextMenu(event: MouseEvent): void {
        this.customContextMenuService .show.next({
            contextMenu: this.testContextMenu,
            event,
            item: this.item
        });
        event.preventDefault();
        event.stopPropagation();
    }

                    <customcontextmenu  #testContextMenu>
                        <ng-template contextMenuItem let-item>
                            I am {{ item?.name }}
                        </ng-template>
                    </customcontextmenu>

I apologize if i bother you.

Jamlearner commented 6 years ago

UPDATE:

I found my silly mistake on why i get "I am " instead of "I am Jam".

I am attaching the contextmenu to a single Element, but I had a mistake of having an array of objects this.items

    items = [
        { ..... },
        { ..... }
    ];

it should be just

    items = { ..... };
isaacplmann commented 6 years ago

You're not bothering me at all. I'm glad you chose my library to learn from. If you come up with something cool, consider writing a PR to share it with others.

Let me know if I can explain anything else.

Jamlearner commented 6 years ago

Thanks for spending some time talking through my issues.

I might need some help on calling custom function testFunction written in CustomMenuComponent when contextmenu is in different component. This is really a helpful feature if I can do something like an API and use it like this.customMenuComponent.testFunction().

ExtendableContextMenuComponent

// copy @Component info from contextMenu.component.ts
@Component({
  encapsulation: ViewEncapsulation.None,
  selector: 'customcontextmenu',
  styles: [`
 ... copied styles from contextMenu.component.ts ...
  `],
  template: ` `,
})
export class ExtendableContextMenuComponent extends ContextMenuComponent {
  // intentionally empty
}

CustomMenuComponent

@Directive({
  selector: 'customcontextmenu',
  // normal stuff
})
export class CustomMenuComponent {
   constructor(
   @Host() private contextMenu: ExtendableContextMenuComponent
   ...
   /* whatever other things you want in here */) {
  }

...
...

   testFunction() { ... }
}

contextmenu is in different component, so i passed customContextMenu with @input to ChildComponent

<app-child [customContextMenu]="customContextMenu"></app-child>
<customcontextmenu #customContextMenu>
... ...
</customcontextmenu>

In ChildComponent, I am attempting to call testFunction() with an error TypeError: this.customContextMenu.testFunction() is not a function.

@Component({
    selector: 'app-child ',
})
export class ChildComponent implements OnInit {
    @Input() customContextMenu: CustomContextMenu;

    ngOnInit() {
        this.customContextMenu.testFunction() <----- error
    }
}

What I understand for now is there will be error if I use @input() customContextMenu: CustomContextMenu when contextmenu is in different component,

but when contextmenu is in the same component, I can use @ViewChild with no issues calling testFunction(). I am little confuse on how these works for now...

isaacplmann commented 6 years ago

That code looks right to me. If this is just a learning project, why don't you put the repo up on GitHub so I can see the whole thing.

On Mar 7, 2018 1:41 AM, "Jamlearner" notifications@github.com wrote:

Thanks for spending some time talking through my issues.

I might need some help on calling custom function testFunction written in CustomMenuComponent when contextmenu is in different component.

ExtendableContextMenuComponent

// copy @Component info from contextMenu.component.ts @Component({ encapsulation: ViewEncapsulation.None, selector: 'customcontextmenu', styles: [ ... copied styles from contextMenu.component.ts ... ], template: , }) export class ExtendableContextMenuComponent extends ContextMenuComponent { // intentionally empty }

CustomMenuComponent

@Directive({ selector: 'customcontextmenu', // normal stuff }) export class CustomMenuComponent { constructor( @Host() private contextMenu: ExtendableContextMenuComponent ... / whatever other things you want in here /) { }

... ...

testFunction() { ... } }

contextmenu is in different component, so i passed customContextMenu with @input to ChildComponent

<app-child [customContextMenu]="customContextMenu"> <customcontextmenu #customContextMenu> ... ...

In ChildComponent, I am attempting to call testFunction() with an error TypeError: this.customContextMenu.testFunction() is not a function.

@Component({ selector: 'app-child ', }) export class ChildComponent implements OnInit { @Input() customContextMenu: CustomContextMenu;

ngOnInit() {
    this.customContextMenu.testFunction() <----- error
}

}

What I understand for now is there will be error if I use @input() customContextMenu: CustomContextMenu when contextmenu is in different component,

but when contextmenu is in the same component, I can use @ViewChild with no issues calling testFunction(). I am little confuse on how these works for now...

— You are receiving this because you were mentioned. Reply to this email directly, view it on GitHub https://github.com/isaacplmann/ngx-contextmenu/issues/94#issuecomment-371039335, or mute the thread https://github.com/notifications/unsubscribe-auth/AA0lQMuydOtQotY9gWXqPP31BsBgvl9Hks5tb4EIgaJpZM4SZpje .

isaacplmann commented 6 years ago

This will work if you set your second.component.html to this:

<h1>second.component:</h1>
<h3 style="margin-top:30px;" [contextMenu]="contextMenuDirective" [contextMenuSubject]="items">Manually attaching event if contextmenu in different component</h3>

Or, to make your solution work, do this:

app.component.html

<customcontextmenu #manualAttachingContextMenu="contextMenuDirective">
    <ng-template contextMenuItem let-item>
        Manually attaching contextmenu event " {{ item.name }} "
    </ng-template>
</customcontextmenu>

contextMenu.component.ts

// ...

@Directive({
    // tslint:disable-next-line:directive-selector
    selector: 'customcontextmenu',
    exportAs: 'contextMenuDirective',
})
// tslint:disable-next-line:directive-class-suffix
export class ContextMenuDirective {
  // ...
}

I wrote this article about all the different ways you can use template reference variables.

Jamlearner commented 6 years ago

Great stuffs! I like your articles and I will read all of them. Thanks for writing up useful articles.

First solution gives an error of:

Template parse errors: Can't bind to 'contextMenuDirective' since it isn't a known property of 'app-second'.

Second solution works great!