it-swarm.com.de

Wie man automatisierte dynamische Breadcrumbs mit dem AngularJS + Angular UI Router herstellt

Eine Schlüsselkomponente für Webanwendungen ist die Brotkrümel/Navigation. Mit dem Angular UI Router ist es sinnvoll, die Breadcrumb-Metadaten in die einzelnen Zustände und nicht in Ihre Controller zu integrieren. Das manuelle Erstellen des Breadcrumbs-Objekts für jeden Controller, für den er benötigt wird, ist eine unkomplizierte Aufgabe, die jedoch auch sehr unordentlich ist.

Ich habe einige Lösungen für automatisierte Breadcrumbs mit Angular gesehen, aber um ehrlich zu sein, sind sie ziemlich primitiv. Einige Zustände, wie Dialogfelder oder Seitenbereiche, sollten die Breadcrumbs nicht aktualisieren, aber mit den aktuellen Add-Ons zum Angle gibt es keine Möglichkeit, dies auszudrücken.

Ein weiteres Problem besteht darin, dass Breadcrumbs nicht statisch sind. Wenn Sie beispielsweise zu einer Seite mit Benutzerdetails gehen, sollte der Breadcrumb-Titel wahrscheinlich der vollständige Name des Benutzers und kein generischer Benutzerdetail sein.

Das letzte Problem, das gelöst werden muss, ist die Verwendung aller korrekten Statusparameterwerte für übergeordnete Links. Wenn Sie beispielsweise eine Benutzerdetailseite eines Unternehmens anzeigen, möchten Sie natürlich wissen, dass der übergeordnete Status einen :companyId erfordert.

Gibt es Addons zu angle, die dieses Level an Breadcrumbs unterstützen? Wenn nicht, wie gehe ich am besten vor? Ich möchte meine Controller nicht verstopfen - ich werde viele davon haben - und ich möchte es so automatisiert und schmerzlos wie möglich machen.

Vielen Dank!

34
egervari

Ich habe das selbst vor einiger Zeit gelöst, weil nichts verfügbar war. Ich entschied mich dafür, das data-Objekt nicht zu verwenden, weil wir eigentlich nicht wollen, dass unsere Breadcrumb-Titel von Kindern vererbt werden. Manchmal gibt es modale Dialoge und rechte Bedienfelder, die sich in der Ansicht "untergeordnete Ansichten" befinden, aber sie sollten den Breadcrumb nicht beeinträchtigen. Durch die Verwendung eines breadcrumb-Objekts können wir die automatische Vererbung vermeiden.

Für die eigentliche title-Eigenschaft verwende ich $interpolate. Wir können unsere Breadcrumb-Daten mit dem Auflösungsumfang kombinieren, ohne dass eine Auflösung an einem anderen Ort erfolgen muss. In all den Fällen, die ich hatte, wollte ich sowieso nur den Auflösungsbereich verwenden, so dass dies sehr gut funktioniert. 

Meine Lösung beherrscht auch i18n.

$stateProvider
    .state('courses', {
        url: '/courses',
        template: Templates.viewsContainer(),
        controller: function(Translation) {
            Translation.load('courses');
        },
        breadcrumb: {
            title: 'COURSES.TITLE'
        }
    })
    .state('courses.list', {
        url: "/list",
        templateUrl: 'app/courses/courses.list.html',
        resolve: {
            coursesData: function(Model) {
                return Model.getAll('/courses');
            }
        },
        controller: 'CoursesController'
    })
    // this child is just a slide-out view to add/edit the selected course.
    // It should not add to the breadcrumb - it's technically the same screen.
    .state('courses.list.edit', {
        url: "/:courseId/edit",
        templateUrl: 'app/courses/courses.list.edit.html',
        resolve: {
            course: function(Model, $stateParams) {
                return Model.getOne("/courses", $stateParams.courseId);
            }
        },
        controller: 'CourseFormController'
    })
    // this is a brand new screen, so it should change the breadcrumb
    .state('courses.detail', {
        url: '/:courseId',
        templateUrl: 'app/courses/courses.detail.html',
        controller: 'CourseDetailController',
        resolve: {
            course: function(Model, $stateParams) {
                return Model.getOne('/courses', $stateParams.courseId);
            }
        },
        breadcrumb: {
            title: '{{course.name}}'
        }
    })
    // lots more screens.

Ich wollte die Breadcrumbs nicht an eine Direktive binden, weil ich dachte, dass es mehrere Möglichkeiten gibt, den Breadcrumb in meiner Bewerbung visuell darzustellen. Also habe ich es in einen Dienst gestellt:

.factory("Breadcrumbs", function($state, $translate, $interpolate) {
    var list = [], title;

    function getProperty(object, path) {
        function index(obj, i) {
            return obj[i];
        }

        return path.split('.').reduce(index, object);
    }

    function addBreadcrumb(title, state) {
        list.Push({
            title: title,
            state: state
        });
    }

    function generateBreadcrumbs(state) {
        if(angular.isDefined(state.parent)) {
            generateBreadcrumbs(state.parent);
        }

        if(angular.isDefined(state.breadcrumb)) {
            if(angular.isDefined(state.breadcrumb.title)) {
                addBreadcrumb($interpolate(state.breadcrumb.title)(state.locals.globals), state.name);
            }
        }
    }

    function appendTitle(translation, index) {
        var title = translation;

        if(index < list.length - 1) {
            title += ' > ';
        }

        return title;
    }

    function generateTitle() {
        title = '';

        angular.forEach(list, function(breadcrumb, index) {
            $translate(breadcrumb.title).then(
                function(translation) {
                    title += appendTitle(translation, index);
                }, function(translation) {
                    title += appendTitle(translation, index);
                }
            );
        });
    }

    return {
        generate: function() {
            list = [];

            generateBreadcrumbs($state.$current);
            generateTitle();
        },

        title: function() {
            return title;
        },

        list: function() {
            return list;
        }
    };
})

Die tatsächliche Breadcrumb-Direktive wird dann sehr einfach:

.directive("breadcrumbs", function() {
    return {
        restrict: 'E',
        replace: true,
        priority: 100,
        templateUrl: 'common/directives/breadcrumbs/breadcrumbs.html'
    };
});

Und die Vorlage:

<h2 translate-cloak>
    <ul class="breadcrumbs">
        <li ng-repeat="breadcrumb in Breadcrumbs.list()">
            <a ng-if="breadcrumb.state && !$last" ui-sref="{{breadcrumb.state}}">{{breadcrumb.title | translate}}</a>
            <span class="active" ng-show="$last">{{breadcrumb.title | translate}}</span>
            <span ng-hide="$last" class="divider"></span>
        </li>
    </ul>
</h2>

Der Screenshot hier zeigt, dass es in der Navigation perfekt funktioniert:

enter image description here

Sowie das html <title>-Tag:

enter image description here

PS an das Angular UI Team: Bitte fügen Sie so etwas aus der Box hinzu!

40
egervari

Ich möchte meine Lösung dazu teilen. Es hat den Vorteil, dass in Ihre Steuerungen nichts injiziert werden muss, und unterstützt benannte Breadcrumb-Labels sowie resolve:-Funktionen zur Benennung Ihrer Breadcrumbs. 

Beispielstatus config:

$stateProvider
    .state('home', {
        url: '/',
        ...
        data: {
            displayName: 'Home'
        }
    })
    .state('home.usersList', {
        url: 'users/',
        ...
        data: {
            displayName: 'Users'
        }
    })
    .state('home.userList.detail', {
        url: ':id',
        ...
        data: {
            displayName: '{{ user.name | uppercase }}'
        }
        resolve: {
            user : function($stateParams, userService) {
                return userService.getUser($stateParams.id);
            }
        }
    })

Dann müssen Sie die Position des Breadcrumb-Labels (displayname) in einem Attribut der Direktive angeben:

<ui-breadcrumbs displayname-property="data.displayName"></ui-breadcrumbs>

Auf diese Weise kann die Direktive den Wert von $state.$current.data.displayName betrachten, um den zu verwendenden Text zu finden. 

$ interpolatierbare Breadcrumb-Namen

Beachten Sie, dass das displayName im letzten Zustand (home.userList.detail) die übliche Winkelinterpolationssyntax {{ value }} verwendet. Auf diese Weise können Sie auf alle Werte verweisen, die im Objekt resolve in der Statuskonfiguration definiert sind. Normalerweise wird dies verwendet, um Daten vom Server abzurufen, wie im obigen Beispiel des Benutzernamens. Da dies nur eine reguläre Winkelfolge ist, können Sie einen beliebigen Typ gültiger Winkelausdrücke in das Feld "Select" aufnehmen - wie im obigen Beispiel, in dem wir einen Filter darauf anwenden.

Demo

Hier ist eine funktionierende Demo zu Plunker: http://plnkr.co/edit/bBgdxgB91Z6323HLWCzF?p=preview

Code

Ich dachte, es wäre ein bisschen viel, um den gesamten Code hier zu platzieren, also hier auf GitHub: https://github.com/michaelbromley/angularUtils/tree/master/src/directives/uiBreadcrumbs

23
Michael Bromley

Ich habe ein Angular-Modul erstellt, das einen Breadcrumb basierend auf den Zuständen des UI-Routers generiert. Alle Funktionen, über die Sie sprechen, sind enthalten (Ich habe vor kurzem die Möglichkeit hinzugefügt, einen Status im Breadcrumb zu ignorieren, während Sie diesen Beitrag lesen :-)):

Hier ist der Github Repo

Es erlaubt dynamische Beschriftungen, die mit dem Controller-Bereich interpoliert werden (die "tiefste" bei verschachtelten/mehreren Ansichten).

Die Zustandskette kann über Statusoptionen angepasst werden (Siehe API-Referenz )

Das Modul wird mit vordefinierten Vorlagen geliefert und ermöglicht benutzerdefinierte Vorlagen.

4
ncuillery

Ich glaube nicht, dass es eine integrierte Funktionalität gibt, aber alle Tools sind für Sie da, schauen Sie sich die LocationProvider an. Sie könnten einfach Navigationselemente verwenden, die Sie verwenden, und was Sie sonst noch wissen möchten, injizieren Sie es.

Dokumentation

0
Gent

Nachdem ich mich tief in das Innere des UI-Routers eingegraben hatte, wusste ich, wie ich mit aufgelösten Ressourcen einen Breadcrumb erstellen konnte.

Hier ist ein Plunker zu meiner Direktive.

HINWEIS: Ich konnte nicht, dass dieser Code im Plunker ordnungsgemäß funktioniert, aber die Direktive funktioniert in meinem Projekt. routen.js wird nur als Beispiel zur Verfügung gestellt, wie Sie Titel für Ihre Navigationspfade festlegen können.

0
Nailer

Vielen Dank für die von @egervari bereitgestellte Lösung. Für diejenigen, die einige $ stateParams-Eigenschaften zu benutzerdefinierten Daten von Breadcrumbs hinzufügen müssen. Ich habe die Syntax {: id} für den Wert des Schlüssels 'title' erweitert.

.state('courses.detail', {
    url: '/:courseId',
    templateUrl: 'app/courses/courses.detail.html',
    controller: 'CourseDetailController',
    resolve: {
        course: function(Model, $stateParams) {
            return Model.getOne('/courses', $stateParams.courseId);
        }
    },
    breadcrumb: {
        title: 'course {:courseId}'
    }
})

Hier ist ein Plunker Beispiel. Zu Ihrer Information.

0
Indiana