Question

I am experienced as a developer but a beginner in angularjs and I have not used javascript for a while.

My site is separated into several angularjs apps. But two apps could have to display the same "entity" types.

For example my first app is dedicated to books and would receive from an api (for example:http://example.com/Books) the following json data:

[
    { "id":"1", "title":"...", "publisher":"..." },
    { "id":"2", "title":"...", "publisher":"..." },
]

The second app is more general and would receive from an api (for example:http://example.com/Things) the following json data:

{
    "count":"3",
    "items":
        // my two books
        { "type":"book", "data":{ "id":"1", "title":"...", "publisher":"..." } },
        { "type":"book", "data":{ "id":"2", "title":"...", "publisher":"..." } },
        // and other things
        { "type":"movie", "data":{ "id":"45", "title":"...", "producer":"..." } },
    ]
}

I created a template "book.html" :

<div>{{book.title}} ({{book.publisher}})</div>

I use it in my view with :

<ul><li ng-repeat="book in books" ng-include="'book.html'"/></ul>

So far so good...

I now would like to use the same template - without duplicating it - to display the books in the first case and in the second with something like :

<ul><li ng-repeat="item in items" ng-include="item.type+'.html'"/></ul>

My issue is not dynamic templates. My issue is: in my book.html template, I use "book.title", but for my second app, I would need to use "item.data.title".

The ideal, in my mind, would have been something like :

<ul><li ng-repeat="item in items" ng-include="item.type+'.html'" ng-something="book=item.data"/></ul>

I could resolve it by "converting" my books array into the second format. But I think I misunderstood something and perhaps I am using angularjs in a wrong way.

Could you please give me some clues ?

Thanks

Was it helpful?

Solution

Use directives. They look intimidating at first, but they are quite simple for such use cases:

app.directive("book", function() {
    return {
        restrict: "A",
        templateUrl: "book.html",
        scope: {
            book: "="
        }
}); // isnt' it simple?

First case:

<ul>
    <li ng-repeat="book in books">
        <span book="book"></span>
    </li>
</ul>

Second case:

<ul>
    <li ng-repeat="item in items" ng-switch="item.type">
        <span ng-switch-when="book" book="item.data"></span>
        <span ng-switch-when="movie" movie="item.data"></span>
        ...
    </li>
</ul>

OTHER TIPS

Angular has a built-in directive called ng-init that can be used to evaluate expressions on a scope, meaning it can be used to set scope-variables without having to create a controller or a custom directive.

In this case

    <li ng-repeat="item in items" 
        ng-include="item.type+'.html'" 
        ng-init="book=item.data"/>

Should do the trick, although the documentation specifies that it is not intended for this use-case:

The only appropriate use of ngInit is for aliasing special properties of ngRepeat [$index, $even etc], as seen in the demo below. Besides this case, you should use controllers rather than ngInit to initialize values on a scope.

https://docs.angularjs.org/api/ng/directive/ngInit

If you don't want to change the structure of your templates, you will need to change the structure of your data. Create a controller:

app.controller('BookOrMovieController', function($scope) {
    if ($scope.item.type == 'Book') {
        $scope.book = $scope.item.data;
    }
    if ($scope.item.type == 'Movie') {
        $scope.movie = $scope.item.data;
    }
});

Then instantiate the controller on the included template element:

<ul><li ng-repeat="item in items" ng-include="item.type+'.html'" ng-controller="BookOrMovieController"/></ul>
Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top