Если вы пытаетесь перейти от angular 1 (и вас не интересует angular 2), похоже, у вас есть два варианта: вы можете либо попробовать заменить отдельные компоненты и съесть angular изнутри, либо вы можете попробовать съесть это снаружи внутрь, заменив сначала роутер.

Мне интересно попытаться выйти из-под маршрутизатора angular 1, поэтому я действительно хочу выбрать последний вариант. Это позволяет мне рассматривать angular 1 как один из способов рендеринга компонентов, а не как фреймворк, отвечающий за все.

Технология, на которую меня больше всего интересуют, - это Elm, и я был вдохновлен докладом Ричарда Фельдмана о веб-компонентах в Elm, и мне казалось, что мы должны достичь чего-то подобного с компонентами / директивами angular 1.

Отобразить директиву angular просто, используя функцию «node» из модуля Elm Html.Attributes; проблема в том, что он ничего не будет делать, если фреймворк angular не узнает, что он был добавлен в DOM, и не получит возможность его скомпилировать.

Например, предположим, что у меня есть следующий угловой компонент:

angular.module('MyApp', [])
    .component('pageOne', {
    template: '<div>Page One</div>',
    controller: function PageOneController () {
        console.log('we are in page one');
    }
});

Я могу визуализировать это из Вяза так:

node "page-one" [] []

Но мне это не помогает, потому что angular ничего об этом не знает. Об этом нужно сказать angular.

Elm, конечно же, генерирует виртуальный DOM, и у нас нет очевидного способа узнать, когда реальный DOM был обновлен.

Наблюдатели за мутациями

Таким образом, с помощью решения маршрутизации на стороне клиента мы эффективно меняем местами содержимое одного корневого узла и из него при изменении маршрута. Мы можем использовать наблюдатель мутаций для отслеживания этого корневого узла, а затем дать команду angular компилировать содержимое при изменении.

Что-то вроде этого:

var root = document.getElementById('root');
var observer = new MutationObserver(triggerDigest);
observer.observe(root, { childList: true, subtree: true });
function triggerDigest() {
    var $body = angular.element(document.body);            
    var $rootScope = $body.injector().get('$rootScope');  
    var $compile = $body.injector().get('$compile');
    $rootScope.$apply(function() {
        $compile($body)($rootScope);
    });
}

Итак, здесь мы получаем ссылку на корневой элемент, в который мы будем встраивать приложение Elm. Мы создаем наблюдателя мутаций, который будет вызывать функцию triggerDigest. Эта функция получает ссылку на $ rootScope angular и его службу компиляции $. Затем он компилирует все дерево в соответствии с корневой областью.

Бесконечные петли

Но у нас есть проблема. В настоящее время мы отслеживаем все поддерево, и компиляция директивы angular вызовет новые мутации и вызовет еще одну компиляцию. Это приведет к бесконечной спирали компиляции и мутации и уничтожит страницу.

Одно из решений - заставить Elm сообщать нам, когда мы должны начать наблюдение, а затем прекратить наблюдение, как только мы скомпилировали. Это можно сделать с помощью портов. Мы можем создать исходящий порт в Elm следующим образом:

port watchDom : String -> Cmd msg

Затем нам нужно отправить сообщение на этот порт при изменении URL-адреса:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        UrlChange location ->
            ( { model | route = Url.parsePath route location }
            , (watchDom "")
            )

Затем мы можем изменить наш javascript следующим образом:

var root = document.getElementById('root');
var app = Elm.Main.embed(root);
var observer = new MutationObserver(triggerDigest);
app.ports.watchDom.subscribe(function(msg) {
    observer.observe(root, { childList: true, subtree: true });
});
function triggerDigest() {
    var $body = angular.element(document.body);            
    var $rootScope = $body.injector().get('$rootScope');  
    var $compile = $body.injector().get('$compile');
    $rootScope.$apply(function() {
        $compile($body)($rootScope);
    });
    observer.disconnect();
}

И это разорвет петлю для нас и оставит нас с началом жизнеспособного решения. Не уверен, насколько это эффективно, но, похоже, по крайней мере, работает.

Одно предостережение при использовании портов таким образом заключается в том, что это означает, что отладчик 0.18 не будет работать должным образом. Можно было бы избежать использования портов, обернув функцию `history.pushState` и отслеживая событие` popstate`. Одним из преимуществ портов является то, что они позволяют нам предоставлять некоторые метаданные о маршруте, по которому мы движемся, например. содержит ли он контент, который, возможно, нуждается в компиляции.

Есть еще проблема

Во части второй я исследую, как работать с внутренними угловыми ссылками.

Полный исходный код этого доказательства концепции можно найти здесь.