Search code examples
angularmdc

Understanding the Angular 4 Framework and MDC Webcomponent: Drawer


Main Question

The main issue I am having is understanding how a specific line of code, more specifically a mdc object, (below in the example code provided) in the documentation of the MDC Webcomponent Drawer is used. Apparently I can't post more than 2 links because I don't have enough bs "reputation" points. Therefore, I can't link all the research I have actually done to find the answer for myself...So the Drawer example is the working example and source code from it. I have read through these particular resources:

  • Web Components Drawer
  • Drawer Example
  • Drawer Example Source Code
  • Material Components Web Components mdc-drawer Github Project
  • And some other various stack overflow, github examples, and projects

Example Code

The following is the code in its entirety. If I was to paste this directly into the "index.html" file in the src/ directory of the angular4 project, it works correctly so it is clearly something I do not understand about how to get access to the mdc object that is seemingly coming from the material-web-components.js file. I think somehow I need to make the mdc object in that .js file available to my angular4 project and I have researched that quite a bit, but I am probably not asking the right question. In all examples, it just says use the Content Delivery Network (CDN) or reference it locally in your project through the ./assets/ directory or through the node_modules/ folder. I can access the .js file either through the CDN or a local assets/ reference, but it is only available in that top level index.html file. If I try to use it in any other part of the angular4 project, as in the app-root component, it isn't wired up or working. Do I need to make this available somewhere like in a config file, the app.module.ts, an import, etc?

<!DOCTYPE html>
<!--
  Copyright 2016 Google Inc. All rights reserved.

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      https://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License
-->
<html>
  <head>
    <meta charset="utf-8">
    <title>Drawer (Persistent) - Material Components Catalog</title>
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <link rel="icon" type="image/png" href="/images/logo_components_color_2x_web_48dp.png" />
    <script src="../assets/material-components-web.css.js" charset="utf-8"></script>
    <script src="../assets/demo-styles.css.js" charset="utf-8"></script>
    <link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:300,400,500">
    <link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
    <style>
    /* Ensure layout covers the entire screen. */
    html {
      height: 100%;
    }

    /* Place drawer and content side by side. */
    .demo-body {
      display: flex;
      flex-direction: row;
      padding: 0;
      margin: 0;
      box-sizing: border-box;
      height: 100%;
      width: 100%;
    }

    /* Stack toolbar and main on top of each other. */
    .demo-content {
      display: inline-flex;
      flex-direction: column;
      flex-grow: 1;
      height: 100%;
      box-sizing: border-box;
    }

    .demo-main {
      padding-left: 16px;
    }
    </style>
  </head>
  <body class="demo-body mdc-typography">
    <aside class="mdc-persistent-drawer">
      <nav class="mdc-persistent-drawer__drawer">
        <div class="mdc-persistent-drawer__toolbar-spacer"></div>
        <div class="mdc-list-group">
          <nav class="mdc-list">
            <a class="mdc-list-item mdc-persistent-drawer--selected" href="#">
              <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">inbox</i>Inbox
            </a>
            <a class="mdc-list-item" href="#">
              <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">star</i>Star
            </a>
            <a class="mdc-list-item" href="#">
              <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">send</i>Sent Mail
            </a>
            <a class="mdc-list-item" href="#">
              <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">drafts</i>Drafts
            </a>
          </nav>

          <hr class="mdc-list-divider">

          <nav class="mdc-list">
              <a class="mdc-list-item" href="#">
                <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">email</i>All Mail
              </a>
              <a class="mdc-list-item" href="#">
                <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">delete</i>Trash
              </a>
              <a class="mdc-list-item" href="#">
                <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">report</i>Spam
              </a>
            </nav>
          </div>
      </nav>
    </aside>
    <div class="demo-content">
      <header class="mdc-toolbar mdc-elevation--z4">
        <div class="mdc-toolbar__row">
          <section class="mdc-toolbar__section mdc-toolbar__section--align-start">
            <button class="demo-menu material-icons mdc-toolbar__icon--menu">menu</button>
            <span class="mdc-toolbar__title catalog-title">Persistent Drawer</span>
          </section>
        </div>
      </header>

      <main class="demo-main">
        <h1 class="mdc-typography--display1">Persistent Drawer</h1>
        <p class="mdc-typography--body1">Click the menu icon above to open and close the drawer.</p>
      </main>

      <script src="../assets/material-components-web.js" charset="utf-8"></script>
      <script>
        var drawerEl = document.querySelector('.mdc-persistent-drawer');
        var MDCPersistentDrawer = mdc.drawer.MDCPersistentDrawer;
        var drawer = new MDCPersistentDrawer(drawerEl);
        document.querySelector('.demo-menu').addEventListener('click', function() {
          drawer.open = !drawer.open;
        });
        drawerEl.addEventListener('MDCPersistentDrawer:open', function() {
          console.log('Received MDCPersistentDrawer:open');
        });
        drawerEl.addEventListener('MDCPersistentDrawer:close', function() {
          console.log('Received MDCPersistentDrawer:close');
        });
      </script>
    </div>
  </body>
</html>

Relevant Code

The relevant line is: var MDCPersistentDrawer = mdc.drawer.MDCPersistentDrawer; and the mdc object is not recognized.

The following is the specific code:

      <script src="../assets/material-components-web.js" charset="utf-8"></script>
      <script>
        var drawerEl = document.querySelector('.mdc-persistent-drawer');
        var MDCPersistentDrawer = mdc.drawer.MDCPersistentDrawer;
        var drawer = new MDCPersistentDrawer(drawerEl);
        document.querySelector('.demo-menu').addEventListener('click', function() {
          drawer.open = !drawer.open;
        });
        drawerEl.addEventListener('MDCPersistentDrawer:open', function() {
          console.log('Received MDCPersistentDrawer:open');
        });
        drawerEl.addEventListener('MDCPersistentDrawer:close', function() {
          console.log('Received MDCPersistentDrawer:close');
        });
      </script>

Lacking Understanding

I am new to JavaScript, the MVC framework, Angular2/4/Angular-cli, and integrating the Material Design toolset. I have been teaching myself this for a few months and am trying to formulate the foundational understanding of all of it together. I have already gone through and built the entire Angular "Heroes" example project and read through the surface level documentation with it so I have tried to prepare myself for the concepts and how to work with this particular framework. I feel I am missing something simple (maybe complex) here with this mdc object. In the Github project documentation, they implement the JavaScript a little differently than in the Drawer example, as you can see:

HTML

<aside class="mdc-persistent-drawer mdc-typography">
  <nav class="mdc-persistent-drawer__drawer">
    <header class="mdc-persistent-drawer__header">
      <div class="mdc-persistent-drawer__header-content">
        Header here
      </div>
    </header>
    <nav id="icon-with-text-demo" class="mdc-persistent-drawer__content mdc-list">
      <a class="mdc-list-item mdc-persistent-drawer--selected" href="#">
        <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">inbox</i>Inbox
      </a>
      <a class="mdc-list-item" href="#">
        <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">star</i>Star
      </a>
    </nav>
  </nav>
</aside>

JS:

let drawer = new mdc.drawer.MDCPersistentDrawer(document.querySelector('.mdc-persistent-drawer'));
document.querySelector('.menu').addEventListener('click', () => drawer.open = true);

But they still refer to that mdc object and have access to it somehow. I have already installed all the webcomponents through npm by this: npm install --save material-components-web and followed the directions here (material.io/components/web/) of how to get started.

So what am I missing or not understanding?

Thank you for any help.


Solution

  • Here is what I did to get a basic example to work.

    npm install --save material-components-web

    Once this has been installed, then you need to update the .angular-cli.json file. Add "../node_modules/material-components-web/dist/material-components-web.min.css" below the "styles.css" line in the styles array and "../node_modules/material-components-web/dist/material-components-web.min.js" as an entry in the scripts array.

    I'm not familiar with this material-components-web library, but to keep the example consistent with yours, I updated the src/index.html to

    <!doctype html>
    <html lang="en">
    <head>
      <meta charset="utf-8">
      <title>TestMcw</title>
      <base href="/">
    
      <meta name="viewport" content="width=device-width, initial-scale=1">
      <link rel="icon" type="image/x-icon" href="favicon.ico">
    </head>
    <body class="demo-body mdc-typography">
      <app-root></app-root>
    </body>
    </html>
    

    And then the src/app/app.component.html to

    <aside class="mdc-persistent-drawer" #drawer>
          <nav class="mdc-persistent-drawer__drawer">
            <div class="mdc-persistent-drawer__toolbar-spacer"></div>
            <div class="mdc-list-group">
              <nav class="mdc-list">
                <a class="mdc-list-item mdc-persistent-drawer--selected" href="#">
                  <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">inbox</i>Inbox
                </a>
                <a class="mdc-list-item" href="#">
                  <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">star</i>Star
                </a>
                <a class="mdc-list-item" href="#">
                  <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">send</i>Sent Mail
                </a>
                <a class="mdc-list-item" href="#">
                  <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">drafts</i>Drafts
                </a>
              </nav>
    
              <hr class="mdc-list-divider">
    
              <nav class="mdc-list">
                  <a class="mdc-list-item" href="#">
                    <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">email</i>All Mail
                  </a>
                  <a class="mdc-list-item" href="#">
                    <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">delete</i>Trash
                  </a>
                  <a class="mdc-list-item" href="#">
                    <i class="material-icons mdc-list-item__start-detail" aria-hidden="true">report</i>Spam
                  </a>
                </nav>
              </div>
          </nav>
        </aside>
        <div class="demo-content">
          <header class="mdc-toolbar mdc-elevation--z4">
            <div class="mdc-toolbar__row">
              <section class="mdc-toolbar__section mdc-toolbar__section--align-start">
                <button class="demo-menu material-icons mdc-toolbar__icon--menu" (click)="toggle()">menu</button>
                <span class="mdc-toolbar__title catalog-title">Persistent Drawer</span>
              </section>
            </div>
          </header>
    
          <main class="demo-main">
            <h1 class="mdc-typography--display1">Persistent Drawer</h1>
            <p class="mdc-typography--body1">Click the menu icon above to open and close the drawer.</p>
          </main>
    </div>
    

    And finally the src/app/app.component.ts file to

    import { AfterViewInit, Component, ElementRef, ViewChild } from '@angular/core';
    
    declare var mdc: any;
    
    @Component({
      selector: 'app-root',
      templateUrl: './app.component.html',
      styleUrls: ['./app.component.css']
    })
    export class AppComponent implements AfterViewInit {
      @ViewChild('drawer') drawerEl: ElementRef;
    
      drawer: any;
      ngAfterViewInit(): void {
            const MDCPersistentDrawer = mdc.drawer.MDCPersistentDrawer;
            this.drawer = new MDCPersistentDrawer(this.drawerEl.nativeElement);
            this.drawerEl.nativeElement.addEventListener('MDCPersistentDrawer:open', function() {
              console.log('Received MDCPersistentDrawer:open');
            });
            this.drawerEl.nativeElement.addEventListener('MDCPersistentDrawer:close', function() {
              console.log('Received MDCPersistentDrawer:close');
            });
      }
    
      toggle() {
        this.drawer.open = !this.drawer.open;
      }
    }
    

    There are a number of things here to note. The #drawer in the html, paired with the @ViewChild('drawer') drawerEl: ElementRef; in the ts file, gives you the element you need without a document selector.

    The declare var mdc: any; in the ts file helps you with Typescript. A lot of npm packages have a corresponding typescript declarations file that can be installed as follows: npm install --save @types/material-components-web, but this library doesn't seem to have one. The declare statement lets the compiler know there is a var called mdc and it is of type 'any' which means no type inference can occur, so it will let you access any properties on that object without complaint.

    You can utilize the framework to tap into dom events(see (click) in the html above), but I'm unaware of a way to do the same thing with the framework for custom library events like 'MDCPersistentDrawer:open', so those still need to be setup in the ts file as demonstrated above.

    Hope this helps!


    UPDATE

    Response to your questions:

    1. The difference between the import and the declare statements. import is utilizing JavaScript's newer module feature. Ideally this one should be used. If you use this approach, you can remove the "../node_modules/material-components-web/dist/material-components-web.min.js" from your .angular-cli.json file. If everything is setup properly, then this will allow for Angular cli to do some nice things during AOT when building in prod mode(ng build --prod).

    HOWEVER, with this particular library, there appears to be a bug at the moment because if you use that approach the cli will give you a build error: Unexpected token: name (MDCTabBarFoundation). Having that js file in the script section of your angular-cli.json file puts the mdc variable on the global scope and removes the ability for Angular to do AOT on it, but in this case, it will allow AOT to continue to run for the rest of your application. The declare statement then tells Typescript about that global variable so it won't complain at transpile time.

    1. Their official documentation uses ElementRef. I see what you are saying about their warning, but I don't see any other 'official' way of achieving this type of result.