4
\$\begingroup\$

I am new to AngularJS and I thought of trying something simple: a notification factory that is useful for displaying actions alert messages like "Save successfully!" or "Validation errors: ...".

The factory - first thing I have tried is to define a container to hold all UI element data and use it for binding. Basically, it allows for setting visibility, type, title and the text.

coreModule.factory('notificationMessage', function () {

    // object constructor function
    var notificationClass = function () {
        this.message = {
            messageVisible: false,
            messageType: "alert-success",
            messageTitle: "Success!",
            messageText: ""
        }
    };

    notificationClass.prototype.setMessage = function (isSuccess, messageTitle, messageText) {
        this.message.messageTitle = messageTitle;
        this.message.messageText = messageText;
        this.message.messageType = isSuccess === true ? "alert-success" : "alert-danger";

        if (!messageText)
            this.message.messageVisible = false;
        else
            this.message.messageVisible = true;
    }

    notificationClass.prototype.clearMessage = function () {
        this.message.messageVisible = false;
        this.message.messageText = "";
    }

    var factory = {
        getInstance: function () {
            return new notificationClass();
        }
    }

    return factory;
});

In this case, the controller which needs to control notification data looks like this:

var messageInstance = notificationMessage.getInstance();
$scope.statusMessage = messageInstance.message;
$scope.clearMessage = messageInstance.clearMessage;

The view defines a div that has some attributes bound to the properties of notification instance:

<div class="alert {{statusMessage.messageType}} text-left" role="alert" ng-show="statusMessage.messageVisible" ng-click="clearMessage()" title="Click to hide">
    <strong> {{statusMessage.messageTitle}}</strong>
    <pre>{{statusMessage.messageText}} </pre>
</div>

This solution seems to work ok, but I was not happy that I have to repeat so much code in all the controllers and views that need notifications (basically, every view that allows user to change something). So, I tried a directive approach.

The directive

The directive is supposed to encapsulate as much functionality as possible to minimize overhead code in controller and in view:

coreModule.directive("notificationElement", function() {
    return {
        restrict: "E",
        scope: {
            isError: "=",
            messageTitle: "=",
            messageText: "="
        },
        template: 
            '   <div class="alert {{statusMessage.messageType}} text-left" role="alert" ng-show="statusMessage.messageVisible" ng-click="clearMessage()" title="Click to hide"> ' + 
            '       <strong> {{statusMessage.messageTitle}}</strong> ' + 
            '       <pre>{{statusMessage.messageText}} </pre> ' + 
            '   </div>',

        controller: function ($scope, $element, $attrs, notificationMessage) {
            $scope.messageInstance = notificationMessage.getInstance();
            $scope.statusMessage = $scope.messageInstance.message;
            $scope.clearMessage = $scope.messageInstance.clearMessage;

            $scope.clearMessage = function () {
                $scope.messageInstance.clearMessage();
                $scope.messageText = "";
            };
        },

        link: function ($scope, $element, $attrs, ctrl) {

            // any change in core attributes will change instance properties
            $scope.$watchGroup(["isError", "messageTitle", "messageText"], function (newValues, oldValues, scope) {
                var newIsError = newValues[0];
                var newMessageTitle = newValues[1];
                var newMessageText = newValues[2];

                $scope.messageInstance.setMessage(newIsError, newMessageTitle, newMessageText);
            });
        }
    };
});

Controller still has to do several things to initialize and allow to easily change notification properties:

theModule.controller('homeController', function($scope, $rootUrlService, $http ) {

    // initializing object to be used for binding
    $scope.statusMessage = { isError: true, messageTitle : "", messageText: "" };
    // ..

    // defines local function, to easily set notification properties
    var setMessage = function (isError, messageTitle, messageText) {
        $scope.statusMessage.isError = isError;
        $scope.statusMessage.messageTitle = messageTitle;
        $scope.statusMessage.messageText = messageText;
    };

    // usage example in the controller
    $scope.onSaveError = function (errorMessage) {
        if (errorMessage) {
            setMessage(false, "Validation failed!", errorMessage);
        }
        else {
            setMessage(false, "Server error!", errorMessage);
        }
    };
}

However, the view is very simple now:

<notification-element is-error="statusMessage.isError" message-title="statusMessage.messageTitle" message-text="statusMessage.messageText">
</notification-element>

Can this be improved in any way (less code in controller, faster execution etc.)?

\$\endgroup\$
  • \$\begingroup\$ Please refrain from creating too many new tags that are mostly specific to your own questions. Always use existing tags whenever possible. Also, please only use backticks for code, not for emphasis. \$\endgroup\$ – Jamal Mar 26 '16 at 2:41
1
\$\begingroup\$

I think, that a good solution can be to create smth like Angular UI modal dialogs. Guys from Angular UI team encapsulated dialog creation in a service, so you can share the same logic of creation of your notifications across all controllers. In this way there is no need to always add a directive creation code in HTML.

\$\endgroup\$
  • \$\begingroup\$ Thanks. I will take a look to see if I can use the same model for my notifications. \$\endgroup\$ – Alexei Apr 9 '16 at 19:47
1
\$\begingroup\$

It has been a while after toying with the notification factory and directive. Now I had to actually use it in several dozens places in a real Web Application. Doing so, I realized how much code must be replicated in controllers and markup.

In order to avoid this, I tried to keep markup and controller code as simple as possible:

The factory:

A factory creates a "class" that takes care of the dirty details of setting visibility, type, text and so on.

coreModule.factory("notificationMessage", [function () {

    // object constructor function
    var notificationClass = function () {
        this.message = {
            messageVisible: false,
            messageType: "alert-success",
            messageTitle: "Success!",
            messageText: "",
            messageTooltip: "Click to hide"
        }
    };

    notificationClass.prototype.getMessage = function () {
        return this.message;
    }

    notificationClass.prototype.setMessage = function (isError, messageTitle, messageText) {
        this.message.messageTitle = messageTitle;
        this.message.messageText = messageText;
        this.message.isError = isError;
        this.message.messageType = isError === false ? "alert-success" : "alert-danger";

        if (!messageText)
            this.message.messageVisible = false;
        else
            this.message.messageVisible = true;
    }

    notificationClass.prototype.hideMessage = function () {
        this.message.messageVisible = false;
    }

    var factory = {
        getInstance: function () {
// ReSharper disable once InconsistentNaming
            return new notificationClass();
        }
    }

    return factory;
}]);

The directive:

Directive is quite simple as it uses a notificationClass instance and uses the factory to delegate functionality.

coreModule.directive("notificationElement", function ($rootUrlService) {
    return {
        restrict: "E",
        scope: {
            messageInstance: "="
        },
        templateUrl: function (elem, attrs) {
            return $rootUrlService.rootUrl + "Scripts/Custom/Common/Templates/notification-element/notification-element.tpl.html";
        },

        controller: function ($scope, $element, $attrs) {

            $scope.clearMessage = function () {
                $scope.messageInstance.hideMessage();
            }
        },

        link: function ($scope/*, $element, $attrs, ctrl*/) {
            $scope.statusMessage = $scope.messageInstance.message;
        }
    };

});

The template:

<!--{{statusMessage}}-->
<div class="alert {{statusMessage.messageType}} text-left alert-notification small-padding" role="alert" ng-show="statusMessage.messageVisible" ng-click="clearMessage()" title="{{statusMessage.messageTooltip}}">
    <strong> {{statusMessage.messageTitle}}</strong>
    <pre>{{statusMessage.messageText}} </pre>
</div>

Usage in markup:

It is very concise, as I want notification to look homogeneously across the entire application. Customization is possible through methods exposed by the factory:

<notification-element message-instance="notificationHelper"></notification-element>

Usage in the controller:

someModule.controller('mainController', function ($scope, notificationMessage) {

    // initialization
    $scope.notificationHelper = notificationMessage.getInstance();

    // clearing the notification
    $scope.notificationHelper.hideMessage();

    // setting some error
    $scope.notificationHelper.setMessage(true, "Load failure", "Loading items failed.");

}
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.