6012
Views

In this tutorial, we’ll take the angular material starter app example discussed during ng-conf 2015 by Thomas Burleson and Naomi Black http://youtu.be/Qi31oO5u33U, and apply it to a MEAN Stack App.

To keep the app simple, I’ve decided to use the Mean Stack ‘Core’ module to set up the views, sidebar, and bottom sheet. In the real world, it’s more likely for these items to be placed within a vertical module but the point of this post is to help you get familiar with the key angular-material concepts, and where they sit within the stack.

Key Features

Sidebar (md-sidenav)

The starter app will have a toggle-able sidebar, which will start off closed for a mobile app, and open up either
i) when the hamburger menu is selected by a user, or
ii) when the screen size is larger than a mobile (e.g. tablet or desktop).

Content Area (md-content)

When a record is selected in the sidebar, the details for that record will display on the right, in the main content area.

Context Menu ($mdBottomSheet)

When the ‘Share’ button is selected, the ‘bottom sheet’ menu will appear with a list of contact options for the selected record.

Prerequisites:

Here are a couple of steps that will help you get started with the boilerplate MeanJS App:

  1. Setup MEAN.js 0.4.0
  2. Add a MongoDB to your app
  3. Add Angular-Material to your app

Once you have a boilerplate MeanJS App, you’re ready to be a Material Girl 😀

Lets do this!

Step 1: Layout Server View

Navigate to modules/core/server/views/layout.server.view.html, and find this code:

	<header data-ng-include="'/modules/core/views/header.client.view.html'" 
                class="navbar navbar-fixed-top navbar-inverse"></header>
	<section class="content">
		<section class="container">
			{% block content %}{% endblock %}
		</section>
	</section>

Replace the code above with the code below. This will set up our app layout, with the navigation bar at the top, and the app content under it.

    <div flex layout-fill layout="column">
        <!-- Container #1 Top -->
        <header data-ng-include="'/modules/core/views/header.client.view.html'"></header>
        <!-- Container #2 Bottom -->
        {% block content %}{% endblock %}
    </div>

Step 2: Index Server View

Navigate to modules/core/server/views/index.server.view.html, and find this code:

	<section data-ui-view></section>

Add the following attributes to the section tag to make sure that it can ‘flex’ with your app

	<section data-ui-view flex layout="column"></section>

Step 3: Init Core Client App File

Navigate to modules/core/client/app/init.js, and find this code:

// Setting HTML5 Location Mode
angular.module(ApplicationConfiguration.applicationModuleName).config(['$locationProvider',
	function($locationProvider) {
		$locationProvider.html5Mode(true).hashPrefix('!');
	}
]);

Replace the code above with the code below. This will allow you to set themes and display icons in your app

// Setting HTML5 Location Mode
angular.module(ApplicationConfiguration.applicationModuleName).config([
        '$locationProvider', '$mdThemingProvider', '$mdIconProvider',
	function($locationProvider, $mdThemingProvider, $mdIconProvider) {
		$locationProvider.html5Mode(true).hashPrefix('!');

        $mdThemingProvider.theme('default')
            .primaryPalette('purple')
            .accentPalette('red');

        // Register the user `avatar` icons
        $mdIconProvider
            .defaultIconSet('./assets/svg/avatars.svg', 128)
            .icon('menu'       , './assets/svg/menu.svg'        , 24)
            .icon('share'      , './assets/svg/share.svg'       , 24)
            .icon('google_plus', './assets/svg/google_plus.svg' , 512)
            .icon('hangouts'   , './assets/svg/hangouts.svg'    , 512)
            .icon('twitter'    , './assets/svg/twitter.svg'     , 512)
            .icon('phone'      , './assets/svg/phone.svg'       , 512);
	}
]);

Step 4: Header Client View

Navigate to modules/core/client/views/header.client.view.html, comment out or remove all of the existing code in the file, and add the following to show the material toolbar:

<div data-ng-controller="HeaderController" flex>
    <md-toolbar layout="row" data-ng-controller="HomeController as ul">
        <md-button class="menu" hide-gt-sm ng-click="ul.toggleList()" 
                   aria-label="Show User List">
            <md-icon md-svg-icon="menu" ></md-icon>
        </md-button>
        <h1>Bossable Material Starter App</h1>
    </md-toolbar>
</div>

Changes for 0.9.x

* md-item renamed to md-list-item

Step 5: Home Client View

Navigate to modules/core/client/views/home.client.view.html, comment out or remove all of the existing code in the file, and add the following to show the Sidebar on the left, and the main content area on the right (the left and right layouts are set by using the layout=”row” here):

<div flex layout="row" data-ng-controller="HomeController as ul">

    <!-- Container #3 Left -->
    <md-sidenav md-is-locked-open="$mdMedia('gt-sm')" md-component-id="left" 
class="md-whiteframe-z2">
        <md-list>
            <md-item ng-repeat="it in ul.users">
                <md-button ng-click="ul.selectUser(it)" 
                           ng-class="{'selected' : it === ul.selected }">
                    <md-icon md-svg-icon="{{it.avatar}}" class="avatar"></md-icon>
                    {{it.name}}
                </md-button>
            </md-item>
        </md-list>
    </md-sidenav>

    <!-- Container #4 Right -->
    <md-content flex id="content">
        <!-- User details sample -->
        <md-icon md-svg-icon="{{ul.selected.avatar}}" class="avatar"></md-icon>
        <h2>{{ul.selected.name}}</h2>
        <p>{{ul.selected.content}}</p>

        <md-button class="share" md-no-ink ng-click="ul.share($event)" 
                   aria-label="Share">
            <md-icon md-svg-icon="share" ></md-icon>
        </md-button>
    </md-content>
</div>

Step 6: Home Client Controller

Navigate to modules/core/client/controllers/home.client.controller.js, add the following code to load the users, show/hide the Sidebar, and show the bottom sheet when the Share button is selected:

'use strict';

angular.module('core').controller('HomeController', ['$scope', 'Authentication', 
'usersService', '$mdSidenav', '$mdBottomSheet', '$log',
    function($scope, Authentication, usersService, $mdSidenav, $mdBottomSheet, $log) {
        // This provides Authentication context.
        $scope.authentication = Authentication;


        /**
         * Main Controller for the Angular Material Starter App
         * @param $scope
         * @param $mdSidenav
         * @param avatarsService
         * @constructor
         */


        // Load all registered users

        usersService
            .loadAll()
            .then( function( users ) {
                self.users    = [].concat(users);
                self.selected = users[0];
            });

        // *********************************
        // Internal methods
        // *********************************

        /**
         * Hide or Show the 'left' sideNav area
         */
        function toggleUsersList() {
            $mdSidenav('left').toggle();
        }

        /**
         * Select the current avatars
         * @param menuId
         */
        function selectUser ( user ) {
            self.selected = angular.isNumber(user) ? $scope.users[user] : user;
            self.toggleList();
        }

        /**
         * Show the bottom sheet
         */
        function share($event) {
            var user = self.selected;

            /**
             * Bottom Sheet controller for the Avatar Actions
             */
            function UserSheetController( $mdBottomSheet ) {
                this.user = user;
                this.items = [
                    { name: 'Phone'       , icon: 'phone'       },
                    { name: 'Twitter'     , icon: 'twitter'     },
                    { name: 'Google+'     , icon: 'google_plus' },
                    { name: 'Hangout'     , icon: 'hangouts'    }
                ];
                this.performAction = function(action) {
                    $mdBottomSheet.hide(action);
                };
            }

            $mdBottomSheet.show({
                parent: angular.element(document.getElementById('content')),
                templateUrl: 'modules/core/views/contactSheet.html',
                controller: [ '$mdBottomSheet', UserSheetController],
                controllerAs: 'vm',
                bindToController : true,
                targetEvent: $event
            }).then(function(clickedItem) {
                $log.debug( clickedItem.name + ' clicked!');
            });


        }

        var self = this;

        self.selected     = null;
        self.users        = [ ];
        self.selectUser   = selectUser;
        self.toggleList   = toggleUsersList;
        self.share        = share;

    }
]);

Step 7: ContactSheet Template

Create a new html file called contactSheet.html in the following location: modules/core/client/views/contactSheet.html. Add the following code to this file to create the contents of the Bottom Sheet, which will display when the ‘Share’ button is selected:

<md-bottom-sheet class="md-list md-has-header">

    <md-subheader>
        Contact <span class="name">{{ vm.user.name }}</span>:
    </md-subheader>

    <md-list>
        <md-item ng-repeat="item in vm.items">
            <md-button ng-click="vm.performAction(item)">
                <md-icon md-svg-icon="{{ item.icon }}"></md-icon>
                {{item.name}}
            </md-button>
        </md-item>
    </md-list>

</md-bottom-sheet>

Step 8: Data Service

Create a new js file called dataService.js in the following location: modules/core/client/services/dataService.js. Add the following code to this file, to default a static list of users and their data in our app:

    'use strict';
angular.module('core').service('usersService', ['$q',
    function($q) {


    /**
     * Users DataService
     * Uses embedded, hard-coded data model; acts asynchronously to simulate
     * remote data service call(s).
     *
     * @returns {{loadAll: Function}}
     * @constructor
     */

    var users = [
        {
            name: 'Lia Lugo',
            avatar: 'svg-1',
            content: 'I love cheese, especially airedale queso. Cheese and biscuits halloumi cauliflower cheese cottage cheese swiss boursin fondue caerphilly. Cow port-salut camembert de normandie macaroni cheese feta who moved my cheese babybel boursin. Red leicester roquefort boursin squirty cheese jarlsberg blue castello caerphilly chalk and cheese. Lancashire.'
        },
        {
            name: 'George Duke',
            avatar: 'svg-2',
            content: 'Zombie ipsum reversus ab viral inferno, nam rick grimes malum cerebro. De carne lumbering animata corpora quaeritis. Summus brains sit​​, morbo vel maleficia? De apocalypsi gorger omero undead survivor dictum mauris.'
        },
        {
            name: 'Gener Delosreyes',
            avatar: 'svg-3',
            content: 'Raw denim pour-over readymade Etsy Pitchfork. Four dollar toast pickled locavore bitters McSweeneys blog. Try-hard art party Shoreditch selfies. Odd Future butcher VHS, disrupt pop-up Thundercats chillwave vinyl jean shorts taxidermy master cleanse letterpress Wes Anderson mustache Helvetica. Schlitz bicycle rights chillwave irony lumberhungry Kickstarter next level sriracha typewriter Intelligentsia, migas kogi heirloom tousled. Disrupt 3 wolf moon lomo four loko. Pug mlkshk fanny pack literally hoodie bespoke, put a bird on it Marfa messenger bag kogi VHS.'
        },
        {
            name: 'Lawrence Ray',
            avatar: 'svg-4',
            content: 'Scratch the furniture spit up on light gray carpet instead of adjacent linoleum so eat a plant, kill a hand pelt around the house and up and down stairs chasing phantoms run in circles, or claw drapes. Always hungry pelt around the house and up and down stairs chasing phantoms.'
        },
        {
            name: 'Ernesto Urbina',
            avatar: 'svg-5',
            content: 'Webtwo ipsum dolor sit amet, eskobo chumby doostang bebo. Bubbli greplin stypi prezi mzinga heroku wakoopa, shopify airbnb dogster dopplr gooru jumo, reddit plickers edmodo stypi zillow etsy.'
        },
        {
            name: 'Gani Ferrer',
            avatar: 'svg-6',
            content: 'Lebowski ipsum yeah? What do you think happens when you get rad? You turn in your library card? Get a new drivers license? Stop being awesome? Dolor sit amet, consectetur adipiscing elit praesent ac magna justo pellentesque ac lectus. You dont go out and make a living dressed like that in the middle of a weekday. Quis elit blandit fringilla a ut turpis praesent felis ligula, malesuada suscipit malesuada.'
        }
    ];

    // Promise-based API
    return {
        loadAll : function() {
            // Simulate async nature of real remote calls
            return $q.when(users);
        }
    };
}]);

Step 9: CSS and Icons

CSS:

Create a new CSS file, for example: modules/core/client/css/md-starter.css
Copy across the CSS from here https://github.com/angular/material-start/blob/es5/app/assets/app.css and paste it into the new file that you have created (md-starter.css).

Icons:

Create a new 'Assets' folder under the 'Public' folder, like so: public/assets
Copy across the svg file (and all of it's contents) from here: https://github.com/angular/material-start/tree/es5/app/assets

That should about do it! If you made it this far, Nice work 🙂

AngularJS Material Starter App in the Mean Stack

15 Comments

Let me know what you think

  • Drew Barfield
    Reply

    The purchased version of this project has good SVG icons. It works perfectly. The project may be upgraded to Node.js 4.x or 5.x if you set the versions of the packages in “bower.json” and “package.json” to “latest”. Then do a “npm install”. There are a few code changes related to glob that need to be made: Find the code by Googling the error message about the callback.

  • Versia Ludvic
    Reply

    Hello again!
    Found the problem to the icons issue. Some of the SVGs in the link are corrupt in some way. The avatars and avatar-set are not available. Same with the Share svg. The rest are fine and downloadable.

    Still having the load errors coming up though. And console logs error every time you select a user on the side-nav:

    TypeError: Cannot read property ‘querySelector’ of undefined
    at extractFromSet (angular-material.js:8016)
    at processQueue (angular.js:14567)
    at angular.js:14583
    at Scope.$get.Scope.$eval (angular.js:15846)
    at Scope.$get.Scope.$digest (angular.js:15657)
    at Scope.$get.Scope.$apply (angular.js:15951)
    at HTMLButtonElement. (angular.js:23303)
    at HTMLButtonElement.eventHandler (angular.js:3271)(anonymous function) @ angular.js:12330$get @ angular.js:9109processQueue @ angular.js:14575(anonymous function) @ angular.js:14583$get.Scope.$eval @ angular.js:15846$get.Scope.$digest @ angular.js:15657$get.Scope.$apply @ angular.js:15951(anonymous function) @ angular.js:23303eventHandler @ angular.js:3271
    angular.js:12330 Cannot read property ‘querySelector’ of undefined

    Ideas?
    Versia

    • bossable

      Hey Versia,

      That’s a shame to hear about the svgs being corrupt. The ‘querySelector’ error is probably related to a loop somewhere, try commenting out parts of your code to see if you can narrow it down. The material design package is updated relatively frequently, so you could also try updating your version of the package.

  • rajnemani
    Reply

    Hi Shristi,

    The user icons are not showing up. Everything is working fine. I see errors in browser console trying to load icons svg-1, svg-2 etc. Are there some assets that are not referenced in the article or am I missing some thing?

    Thank you for this very informative post!.

    Thanks
    Raj

    • bossable

      Hey Raj – you might be missing a reference to the assets – double check the last few steps

    • Thoai Nguyen

      I keep getting the message when running “bower install angular-material –save”:

      Unable to find a suitable version for angular, please choose one:
      Answer:

      Why do I get this prompt? What should I answer so things can move forward?

      Below is my bower.json file:

      {
      “name”: “meanjs”,
      “version”: “0.4.0”,
      “description”: “Fullstack JavaScript with MongoDB, Express, AngularJS, and Node.js.”,
      “dependencies”: {
      “bootstrap”: “~3”,
      “angular”: “~1.3”,
      “angular-resource”: “~1.3”,
      “angular-animate”: “~1.3”,
      “angular-mocks”: “~1.3”,
      “angular-bootstrap”: “~0.11.0”,
      “angular-ui-utils”: “~0.1.1”,
      “angular-ui-router”: “~0.2.10”,
      “angular-file-upload”: “~1.1.5”,
      “angular-material”: “~0.7.1”
      }
      }

    • bossable

      You can select any option that refers to 1.3 or higher. If you prefix your option with ! like !1, then it’ll save it in your bower.json file for you.

    • Henry

      Did you fix the issue? Got the same one: unable to find “$mdThemingProvider” but able to register “ngMaterial”

  • Zach Sosana
    Reply

    Can we keep the tutorials on angular material coming? tons to learn about and love to have you teach us!! really intrigued by their animations and transitions aspect.