Search code examples
javascriptangularjsstring-interpolation

AngularJS refresh interpolated value


I'm trying to figure out, how to update interpolated values in an Angular template - if the value itself does not change.

I have statements like this (e.g. a translation):

<div>{{ 'SOME_TAG' | lang }}</div>

This starts a filter

app.filter('lang', function(Locale) {
    return function(val) {
        return Locale.translate(val);
    }
});

Locale is a service that has a field lang, depending on which the values are translated into different languages. When I change the value of this field, the value of the translation should change as well.

Something like

<select ng-model="lang" ng-change="changeLang()">
    <option value="de">Deutsch</option>
    <option value="en">English</option>
</select>

...

$scope.changeLang = function() {
    Locale.changeLanguage($scope.lang);
};

In older AngularJS versions (e.g. 1.1.5, not sure how far) this did the trick just fine. If I update the Locale.lang value and a $digest() cycle runs, the interpolated template values are updated. In the newest version (1.4.6 but also 1.3.xx) this does not work anymore. I assume this is part of some optimization - the value ('SOME_TAG') has not changed, so why rerun the interpolation.

I've seen this http://angular-translate.github.io/ where it works fine. Is there a "trick" to have those values update?

Thank you.


Solution

  • In the angular docs about filters there is something called stateful. It was added to improve performance of filters.

    There's a very good blog post about stateful filters that's describing it in detail.

    In short 'SOME_TAG' is a string and never changes and the $watch that the filter is adding would never be called. But with $stateful it will also check if there is a change in the injected dependency of your filter.

    So you would need $stateful or you could pass your language scope variable to the service to have it working. I think it's better to avoid $stateful filters (as recommended in the docs) because they're running more often then with-out it. So passing the language scope will be better here.

    Please have a look at the demo below or in this fiddle.

    angular.module('demoApp', [])
    	.factory('Locale', Locale)
    	.filter('langStateful', LangFilterState)
    	.filter('lang', LangFilter)
    	.controller('MainController', MainController);
    
    function MainController($scope, $timeout, Locale) {
    	$scope.changeLang = function() {
            Locale.changeLanguage($scope.lang);
        };
    }
    
    function LangFilter(Locale) {
        function LangFilter(val, lang) {
            return Locale.translateLang(val, lang);
        }
        
        return LangFilter;
    }
    
    function LangFilterState(Locale) {
        function LangFilter(val) {
            return Locale.translate(val);
        }
        
        LangFilter.$stateful = true;
        
        return LangFilter;
    }
    
    function Locale() {
    	var localeFactory = {
            language: 'de',
            changeLanguage: changeLanguage,
            translate: translate,
            translateLang: translateLang,
            tags: {
                'title': {
                	de: 'Titel',
                    en: 'title'
                },
                'help': {
                    de: 'Hilfe',
                    en: 'help'
                }
            }
        };
        
        return localeFactory;
        
        function changeLanguage(lang) {
            this.language = lang;
        }
        
        function translate(tag) {
            return this.tags[tag][this.language];
        }
        
        function translateLang(tag, lang) {
            return this.tags[tag][lang];
        }
    }
    <script src="https://cdnjs.cloudflare.com/ajax/libs/angular.js/1.4.6/angular.js"></script>
    <div ng-app="demoApp" ng-controller="MainController">
    <select ng-model="lang" ng-change="changeLang()" ng-init="lang='de'">
        <option value="de">Deutsch</option>
        <option value="en">English</option>
    </select>
        
        {{'help' | langStateful}}
        {{'title' | lang: lang}}
    </div>