InfomediaLtd / angular2-materialize

Angular 2 support for Materialize CSS framework.
https://infomedialtd.github.io/angular2-materialize/
MIT License
409 stars 142 forks source link

*ngIf disables dropdown functionality. #64

Open lbertenasco opened 7 years ago

lbertenasco commented 7 years ago

Hi @rubyboy,

I have the following code:

<ul id="dropdown1" class="dropdown-content">
  <a>dropdown item</a>
</ul>
<nav>
  <div class="nav-wrapper">
    <a class="waves-effect waves-light btn" (click)="toggle()">toggle</a>
    <ul class="right hide-on-med-and-down" *ngIf="drop">
      <li><a materialize="dropdown" class="dropdown-button" data-activates="dropdown1">
        dropdown<i class="material-icons right">arrow_drop_down</i></a>
      </li>
    </ul>
  </div>
</nav>
public drop: boolean = true;

toggle() {
  this.drop = !this.drop;
}

The dropdown works fine until you toggle it off and on. Then it stops working. It seems it's not re-initializing the directive.

Maybe I'm missing something.

BruthaVoodoo commented 7 years ago

@lbertenasco I have run into the same issue. Were you able to find a solution?

rubyboy commented 7 years ago

@lbertenasco @BruthaVoodoo , I think this is happening because the element gets destroyed and recreated when using ngIf and MaterializeCSS needs to be told about it.

See below a workaround. I'll try to figure out a good way to support that internally, but hopefully the workaround should get you going.

@Component({
    selector: "dropdown",
    template: `
     <!-- Dropdown Trigger -->
     <a materialize="dropdown" [materializeActions]="dropdownActions" class='dropdown-button btn' href='#' data-activates='dropdown1'>Drop Me!</a>
     <!-- Dropdown Structure -->
     <ul id='dropdown1' class='dropdown-content' *ngIf="drop">
       <li><a>one</a></li>
       <li><a>two</a></li>
       <li class="divider"></li>
       <li><a>three</a></li>
     </ul>
     <br/><br/>
     <a class="waves-effect waves-light btn" (click)="toggle()">toggle</a>
    `
})
export class Dropdown {
    public drop: boolean = true;
    dropdownActions = new EventEmitter<string|MaterializeAction>();

    toggle() {
      this.drop = !this.drop;
      if (this.drop) {
        this.dropdownActions.emit({action:"dropdown",params:null});
      }
    }
}
LeonardoOlmos commented 7 years ago

It's also happening for collapsibles

rvgarimrj commented 7 years ago

I am with an similar issue here. my component is like below:

<div class="dashboard">
  <div class="row">
    <div *ngIf="hasCompany && isOwner"  class="col s6 m2">
      <ul id="dropdown1" class="dropdown-content">
        <li><a href="">Vendedor<span class="badge"></span></a></li>
        <li><a href="">Fornecedor<span  class="badge"></span></a></li>
        <li><a href="">Produto</a></li>
      </ul>
      <a class="btn dropdown-button" href="" data-activates="dropdown1"><i class="menu material-icons left">mode_edit</i></a>
      <span class="menu_cadastros">Cadastros</span>
    </div>

    <div *ngIf="hasCompany && isOwner" class="col s6 m2">
      <ul id="dropdown2" class="dropdown-content">
      <li><a href="">Compra<span class="badge"></span></a></li>
      <li><a href="">Venda<span class="badge"></span></a></li>
      <li><a href="">Devolução</a></li>
    </ul>
    <a class="btn dropdown-button" href="" data-activates="dropdown2"><i class="menu material-icons left">assignment_turned_in</i></a>
    <span class="menu_pedidos">Pedidos</span>
    </div>

    <div class="col s6 m2">
      <ul id="dropdown3" class="dropdown-content">
      <li><a href="">Vendedor<span class="badge"></span></a></li>
      <li><a href="">Fornecedor<span class="badge"></span></a></li>
      <li><a href="">Produto</a></li>
    </ul>
    <a class="btn dropdown-button" href="" data-activates="dropdown3"><i class="menu material-icons left">monetization_on</i></a>
    <span class="menu_financeiro">Financeiro</span>

    </div>

  <div class="row">
    <div class="col s6 m2">
      <ul id="dropdown4" class="dropdown-content">
      <li><a [routerLink]="['/perfil']">Perfil<span class="badge"></span></a></li>
      <li> <a [routerLink]="['/mudar-senha']">Mudar senha<span class="badge"></span></a></li>
      <li><a [routerLink]="['/cancelar-conta']">Cancelar conta</a></li>
    </ul>
    <a class="btn dropdown-button" href="" data-activates="dropdown4"><i class="menu material-icons left">perm_identity</i></a>
    <span class="menu_conta">Conta</span>

    </div>

    <div class="col s6 m2">
      <ul id="dropdown5" class="dropdown-content">
      <li><a href="">Vendedor<span class="badge"></span></a></li>
      <li><a href="">Fornecedor<span class="badge"></span></a></li>
      <li><a href="">Produto</a></li>
    </ul>
    <a class="btn dropdown-button" href="" data-activates="dropdown5"><i class="menu 
    material-icons left">pie_chart</i></a>
    <span class="menu_relatorios">Relatórios</span>

    </div>
  </div>  
</div>

i am using angular4. but if I click on any span class that has its childs (example: span class=" menu_cadastros" - the first one), it makes a page refresh and only then, if click again, it opens the dropdown structure. how can i fix this ?

julienmarantes commented 6 years ago

I'm having a similar issue with this kind of test :

<div *ngIf="isLoggedIn()">
  <div class="navbar-fixed">
    <nav>
      <div class="nav-wrapper white row">
        <ul class="col s1">
          <li>
            <a materialize="dropdown" class="btn btn-floating waves-effect waves-light blue dropdown-button"
               data-activates='dropdown-menu-main'>
              <i class="material-icons left">menu</i>
            </a>
          </li>
        </ul>
        <div class="col s3 left">
          <div *ngIf="breadcrumb">
            <a *ngFor="let b of breadcrumb" class="breadcrumb grey-text">
              {{b}}
            </a>
          </div>
        </div>
      </div>
    </nav>
  </div>
</div>

This is a navigationBar that is displayed to the user only when he is successfully logged in

*ngIf="isLoggedIn()" is supposed to hide it otherwise. But it also bug the dropwdown and I don't see a way to fix it ?

geogramdotcom commented 6 years ago

Same issue for me when using *ngIf="isLoggedIn". Any word on a fix?

devmubeen commented 6 years ago

Same issue exist for me angular2-materialize plugin when using *ngIf but works fine after refresh

LuisCarrilloR commented 6 years ago

I don't know if it is already fixed but my solution was to put 'window.location.reload()' right after the *ngIf is trigger.

ParikshitChavan commented 6 years ago

The suggested workaround is not false proof. The following correction is needed to make the workaround work as intended for people using *ngIf="isLoggedIn" or in most other cases as well.

toggle() {
  this.drop = !this.drop;
  if (this.drop) {
    setTimeout(()=>{
      this.dropdownActions.emit({action:"dropdown",params:null});
    });
  }
}

If you flip the boolean value to true and in the next line of code you try to get a reference to the component or DOM element controlled by *ngIf... well, that component or DOM element doesn't exist yet. Angular doesn't run in parallel with your code. Your JavaScript callback has to finish, then Angular (change detection) runs, which will notice the boolean value change and create the component or DOM element and insert it into the DOM.

To fix this issue, call setTimeout(callbackFn, 0) after you flip the boolean value. This adds your callbackFn to the JavaScript message queue. This will ensure that Angular (change detection) runs before your callback function. Hence, when your callbackFn executes, the element on which you want to call materealizeactions should now exist. Using setTimeout(..., 0) ensures that your callbackFn gets called in the next turn of the JavaScript event loop.

Hope this helps.